Closed lsnch closed 4 years ago
If I understand your point well, I think you could use requestAnimationFrame twice in your layoutEffect to set the new state, so that the effect don't happen til the next paint.
That's unlikely since the animation is somewhere deep inside Material UI. I have a heavy tree with effects, and a few MUI components change the way my render unfolds.
Let me know if it's a bug and I'll provide a reproduction.
Layout effects are for things that need to happen before paint (e.g. measuring the position of a tooltip). If such an effect needs to re-render (e.g. to re-position the tooltip) then it must happen synchronously, so the user only sees a single paint (e.g. never sees the tooltip in the wrong position to begin with).
As part of this second, synchronous render, React also flushes the remaining/pending effects (including the passive ones- which would otherwise have not been flushed until a later frame).
Layout effects are for things that need to happen before paint (e.g. measuring the position of a tooltip). If such an effect needs to re-render (e.g. to re-position the tooltip) then it must happen synchronously, so the user only sees a single paint (e.g. never sees the tooltip in the wrong position to begin with).
Couldn't agree more.
As part of this second, synchronous render, React also flushes the remaining/pending effects (including the passive ones- which would otherwise have not been flushed until a later frame).
But useEffect is designed exactly for this? To do stuff soon, but not before you have picture? React docs mention this everywhere:
https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects
Also, don’t forget that React defers running useEffect until after the browser has painted
https://reactjs.org/docs/hooks-overview.html#effect-hook
When you call useEffect, you’re telling React to run your “effect” function after flushing changes to the DOM
https://reactjs.org/docs/hooks-reference.html#useeffect
The function passed to useEffect will run after the render is committed to the screen.
https://reactjs.org/docs/hooks-reference.html#timing-of-effects
the function passed to useEffect fires after layout and paint, during a deferred event.
It not some obscure typo in a dark corner of the docs. Rather, it's the defining feature of this hook and it's repeated over and over. I find this behavior inconsistent and contradicting the documentation.
As part of this second, synchronous render, React also flushes the remaining/pending effects (including the passive ones- which would otherwise have not been flushed until a later frame).
That's why I mentioned the part that's bolded. useEffect
is called after paint in the optimal case.
Triggering a re-render (sometimes also called a "cascading render") is a de-opt case, since it requires React to immediately do more rendering work (which delays the paint). This is important for certain cases, like the one I mentioned above (positioning a tooltip), but it's best to avoid if possible because of reasons like we're discussing.
Cleaning up issues and closing this one because the question seems to be answered. 👍
Layout effects are for things that need to happen before paint (e.g. measuring the position of a tooltip). If such an effect needs to re-render (e.g. to re-position the tooltip) then it must happen synchronously, so the user only sees a single paint (e.g. never sees the tooltip in the wrong position to begin with).
As part of this second, synchronous render, React also flushes the remaining/pending effects (including the passive ones- which would otherwise have not been flushed until a later frame).
Hi @bvaughn. Sorry for comenting on such an old post. Found this issue via Google and hopefully you can help me.
I am the author of https://github.com/SrBrahma/react-native-shadow-2. On its current implementation, it first applies the shadow in a relative way (that may have a 1pixel gap/overlap), then gets the children's size with onLayout, setState the exact children's size, and on the next render the exact size is applied to have a perfect SVG shadow sizing and positioning (now it has a specific bug with iOS but that's another issue).
Would be possible to use useLayoutEffect somehow to apply the exact component size on the first render, without painting the intermediary state? onLayout doc says it is called before the render, after the calculation.
Would be possible to use useLayoutEffect somehow to apply the exact component size on the first render, without painting the intermediary state?
The browser won't paint if layout effect is used to schedule a re-render that changes the output of the previous render. The browser will have to calculate layout (for you to read measurements) but no paint will occur until after the 2nd render.
As part of this second, synchronous render, React also flushes the remaining/pending effects (including the passive ones- which would otherwise have not been flushed until a later frame).
This behaviour surprised me too. I don't see it mentioned anywhere in the React documentation, so it was never part of my mental model.
Could we add this to the documentation?
@OliverJAsh Good point. I think this deserves its own entry in https://react.dev/reference/react/useLayoutEffect#caveats. Can you send a PR and ping me in it?
Do you want to request a feature or report a bug?
A bug, but more likely a question.
What is the expected behavior?
I have some pretty intense computations in
useEffect
. And also some trivial animations inuseLayoutEffect
.What I expect of react is to let me paint a page based on what I specified in layout effects. Once it's done it can go on run effects.
What is the current behavior?
What actually happens is if I change state inside layoutEffect, every single effect is run, and not only in this component, but also in every parent up the tree.
If this is expected, why does this happen?