bvaughn / react-virtualized-auto-sizer

Standalone version of the AutoSizer component from react-virtualized
MIT License
613 stars 82 forks source link

Autosizer sets state in componentDidMount causing a nested update #88

Closed dinkinflickaa closed 7 months ago

dinkinflickaa commented 7 months ago

Autosizer sets state in componentDidMount phase which will block browser from repainting. This can be a big performance bottleneck.

Here is a codesandbox to demonstrate how changing state in componentDidMount phase can take longer to paint

https://codesandbox.io/p/sandbox/react-class-forked-5ffvgl?file=%2Fsrc%2Findex.js

Trace with state change inside componentDidMount image

Trace without state change inside componentDidMount image

Has anyone else run into this? Can we get the state changes before the first rather and avoid a nested update?

bvaughn commented 7 months ago

Layout effects are permitted when a component should re-size its contents before the browser paints. (This is done to avoid flickering/shifting.)

The main problem in the above screenshot is either (a) something in your code is triggering repeated renders+updates or (b) one or more of your components is doing too much work in render to be able to update quickly. Hard to tell which.

dinkinflickaa commented 7 months ago

Layout effects are permitted when a component should re-size its contents before the browser paints.

@bvaughn In this case aren't we triggering resize manually though? See: https://github.com/bvaughn/react-virtualized-auto-sizer/blob/master/src/AutoSizer.ts#L65C1-L66C1

The main problem in the above screenshot is either (a) something in your code is triggering repeated renders+updates or (b) one or more of your components is doing too much work in render to be able to update quickly

The screenshot is taken from codesanbox link's browser trace. https://codesandbox.io/p/sandbox/react-class-forked-5ffvgl?file=%2Fsrc%2Findex.js

All it does is:

  1. On Page load renders 10k components. Nothing virtualized.
  2. Each component implements a useEffect mount and cleanup.
  3. On click, Re-creates the class component which changes a text property in componentDidMount

So there aren't repeated re-renders but paint is delayed here because it has a lot of slow side effects which have to be flushed synchronously because of the nested update. This can be quite terrible when you are unmounting a lot of components before mounting AutoSizer

bvaughn commented 7 months ago

I'm not sure what to say, except...don't re-render 10k components like that 😄 It's never going to be performant. Switching from a layout effect to a passive effect will not make a difference in many cases, (often something else will trigger a sync update which will cause React to also flush passive effects before proceeding). And even if that's not the case, it's still going to take a ton of CPU time.

Layout effects are for layout (resizing and positioning) which is exactly what this component does.