React-table: [Performance] All rows are re-rendered on selecting row

Created on 5 Sep 2019  路  11Comments  路  Source: tannerlinsley/react-table

Using v7?

Yes

Describe the bug
Rows are re-rendered again on operation like checkbox click.

Minimal replication is available at https://codesandbox.io/s/tannerlinsleyreact-table-row-selection-vqe7c.

Steps -

  1. Check on a checkbox
  2. In console check logging, each row is being re-rendered, which increase operation time, this happens for most of the operations(not only useRowSelect)

Disclaimer - I may be missing something.

Most helpful comment

My initial thoughts are:

  • Using all of the standard plugin hooks together is performant out of the box, as seen in the kitchen-sink example.
  • Styled Components don't usually affect performance (unless you have a massive number of rapidly changing dynamic styles in them)
  • Virtual scrolling is nice and should give you similar, if not the same performance as pagination if implemented correctly.
  • The only non-avoidable piece of React-Table for rendering a row is calling prepareRow. Doing this for hundreds or thousands of rows in a render is going to be expensive. Pagination and / or virtualization of those rows is necessary if that is your use case.

It's been said before that moving to React hooks would probably uncover a lot of issues in the way we used to write react. This was mostly referring to the way we handle side effects, but I also believe that it has to do with performance issues as well (older versions of React encouraged render-bailout, React hooks encourages more memoization). There are clearly other components out there in the ecosystem that are not very performant, but the only way to find them is to profile. IMO, the easiest way to profile is by isolating the circumstances into a codesandbox and profiling within that isolated context.

So I went ahead and profiled the sandbox you posted above and here are my findings:

  • You're only using the useRowSelect plugin hook. So things should be very fast
  • You aren't using any intense components inside of your Table component or Cell renderers
  • Turned on debug: true in React Table and didn't find any peculiarities with the debug statements.
  • Then noticed that you are rendering all 2,000 rows of the table, all the time. This is why that example is slow. If you simply paginate (or even just rows.slice(0, 20).map(...) in your Table component you'll see the performance jump drastically.
  • Another thing to keep in mind is that Codesandbox runs the development version of React. It's very likely that even with 2000 rows being rendered all the time, it would be slightly faster (albeit, still slow) in the production version of React.

All 11 comments

No you're not missing anything. This response honestly deserves a blog post, but I'll have to link to another one for now: https://kentcdodds.com/blog/react-hooks-pitfalls. Specifically read the section on "Pitfall 4: Overthinking performance".

In a nutshell, rerenders are not bad at all. They are what keep your UI up to date. Expensive rerenders however, can become a problem very quickly. Luckily, React Table v7 is built on this exact premise that rerenders are good, but expensive rerenders are bad.

  • React Table is rerender heavy. This ensures things stay up to date.
  • React Table doesn't use PureComponent or rerender bailouts. If you want to use these in your own markup, feel free to do so, but at your own risk. In most, if not all, scenarios you would do this, it could probably be solved by using a useMemo or useCallback
  • React Table uses extensive memoization, caching and change-detection to ensure that rerenders are extremely performant.

Moral of the story: Only worry about rerenders if they are expensive or degrading performance.

Unfortunately I did have to use memo() on my Row component. It's unbearably slow in development and definitely not snappy on production (default page size is 50 and I can't change it - that is a requirement). I have a prop there called forceUpdate to which I'm passing row.selected. It's ugly, but I guess I don't have any other option aside from maybe using virtualization.

What are your rows/cells rendering? How many columns?

Just to clarify - it's super slow without memo(). With memo it only rerenders the row that got selected so everything is working smoothly.

I have two bigger tables, one of them has 8 columns, 6 of them render text with tooltips (using tippy), one renders custom checkbox and the last one has either 2 buttons or nothing. The other one can technically have unlimited amount of columns, because they're generated dynamically based on data, but realistically it's just 6-8 columns. One has checkbox, second one a single icon, third one text with tooltip and the rest render "progress bars", it renders something like this:

<div className={styles.container}>
      <div className={styles.bar}>
        <div className={classes} style={style} />
      </div>
      <div className={styles.value}>
        <div className="text-center">{customValue || formatNumber(value)}</div>
      </div>
    </div>

where style contains backgroundColor, left and width calculated based on value, the rest are classes from a CSS module. It might be because of those progress bars, but honestly there's not much difference in performance between these two tables. It's all built on styled components (i.e. I have a Row component, Cell component etc.), similarly to how the table was built in early alphas (~alpha.6?) - maybe this is the reason? It's also built on divs with flex plugin, not classic tables. Using profiler doesn't really help - it's just the sheer amount of components to render that makes it slow rather than something specific, but I find it difficult to reduce amount of these components. Perhaps the approach you're using in your examples (a single styled component that styles its children) would perform better? I don't know. For now it works well with memo(), but if you have some suggestions on how to style the table to keep it performant, I'm definitely interested.

My guess is that it's not styled components. IMO, it usually comes down to a few things it could be when things are rendering slowly:

  • Renderers (or the components they render) are not keyed properly across rerenders (react-table tries to do this for you through the built-in prop getters) and they are unmounting / remounting when they don't need to.
  • Renderers (or the components they render) are creating new components on every render (rare)
  • Renderers (or the components they render) are doing expensive things (and probably need to start memoizing / caching

I would need more visibility into your code (a watered down codesandbox would be great) to dig into your specific scenario though.

@paolostyle , using memo is not scalable, as you need to puts hacks like this, which may not be possible(buggy/not maintainable) for other features if you are creating a full-fledged grid with all features.

@tannerlinsley , after going through all docs and posts, i understand your point, that re-renders are OK but non-performant re-render are bad.

But in our case of react-table, we can check the given example only https://codesandbox.io/s/tannerlinsleyreact-table-row-selection-vqe7c( https://vqe7c.csb.app/ ) how can we find non-performant components in this case, because the example given is minimal and take a good amount to time to reflect a simple user action.

I am worried about performance, so that the combination of all hooks + good amount of data + custom code(provided by lib's user) would not cause bad UX.

Because currently by using following things, faced latency on user actions :-

  1. useFilters, useSortBy, useGroupBy, useExpanded, useRowSelect, useRowClick, useFlexLayout
  1. Using emotion for styled components.
  2. Using Vertical virtual scrolling(thinking of implementing horizontal virtual scrolling as well)

What are your thoughts on it ?

My initial thoughts are:

  • Using all of the standard plugin hooks together is performant out of the box, as seen in the kitchen-sink example.
  • Styled Components don't usually affect performance (unless you have a massive number of rapidly changing dynamic styles in them)
  • Virtual scrolling is nice and should give you similar, if not the same performance as pagination if implemented correctly.
  • The only non-avoidable piece of React-Table for rendering a row is calling prepareRow. Doing this for hundreds or thousands of rows in a render is going to be expensive. Pagination and / or virtualization of those rows is necessary if that is your use case.

It's been said before that moving to React hooks would probably uncover a lot of issues in the way we used to write react. This was mostly referring to the way we handle side effects, but I also believe that it has to do with performance issues as well (older versions of React encouraged render-bailout, React hooks encourages more memoization). There are clearly other components out there in the ecosystem that are not very performant, but the only way to find them is to profile. IMO, the easiest way to profile is by isolating the circumstances into a codesandbox and profiling within that isolated context.

So I went ahead and profiled the sandbox you posted above and here are my findings:

  • You're only using the useRowSelect plugin hook. So things should be very fast
  • You aren't using any intense components inside of your Table component or Cell renderers
  • Turned on debug: true in React Table and didn't find any peculiarities with the debug statements.
  • Then noticed that you are rendering all 2,000 rows of the table, all the time. This is why that example is slow. If you simply paginate (or even just rows.slice(0, 20).map(...) in your Table component you'll see the performance jump drastically.
  • Another thing to keep in mind is that Codesandbox runs the development version of React. It's very likely that even with 2000 rows being rendered all the time, it would be slightly faster (albeit, still slow) in the production version of React.

To help illustrate this, take a look at this codesandbox.

You'll notice in the console that as you check the boxes, the timing of useTable is very fast. Less than a millisecond in most cases. But rendering the table is very slow, close to half a second. This is a great way to measure if React Table is being slow, or if your renderer is being slow.

Thank you @tannerlinsley for the detailed explanation, this is much helpful. To decrease the rendering time will scratch more.

Awesome. Closing for now. Thanks!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

missmellyg85 picture missmellyg85  路  3Comments

DaveyEdwards picture DaveyEdwards  路  3Comments

pasichnyk picture pasichnyk  路  3Comments

esetnik picture esetnik  路  3Comments

panfiva picture panfiva  路  3Comments