Assuming the useLayoutEffect should fire _after_ DOM is painted, there's a bug where the elements are not yet flushed to DOM for the initial invocation of useLayoutEffect when it's used in a component that's rendered as a nested child:
Case 1:
return <CompoUsingLayoutEffect/>
Case 2:
return <div>
<CompoUsingLayoutEffect/>
</div>
In case 2, the useLayoutEffect is initially fired _before_ the DOM is painted. While in case 1, it works as expected.
Consecutive executions of useLayoutEffect are called after the DOM is painted.
Best if you check the repro yourself:
Preact: https://codesandbox.io/s/agitated-morse-b6zrp?expanddevtools=1
React: https://codesandbox.io/s/hardcore-hertz-p5up6?expanddevtools=1
In the repros above, all textareas should be initially resized to fit the content, but in Preact the first textarea is initially set to height: 0 because the scrollHeight is read before the textarea is in the DOM (check the console for 0 false which indicates the element isn't in the document).
Preact 10.0.0-rc.1
Could this be addressed short-term by adding another queue?
@developit Probably. It's worth looking into since 2 phase may take some time
I encountered this issue today. The issue isn't about whether the DOM is _painted_ though, but whether the DOM node is _connected_ to the document, as indicated by Node.isConnected.
useLayoutEffect is _supposed_ to fire before the DOM is _painted_ (see the React docs). That is the main difference from useEffect. However it should fire after the DOM node is connected so that you can measure it and do things that depend on these measurements.
I stand corrected. I somehow thought the main difference is that the useEffect fires in the next [animation] frame that comes after the frame in which the DOM was painted, first, while useLayoutEffect fires in the same [animation] frame. I.e., I had (still have) weak mental model of how event loop frames relate to animation frames (and how they relate to browser render, reflow, and paint).
Which I reckon still applies to what you're saying.
So, one should think about it like this?
ANIMATION FRAME (1) ANIMATION FRAME (2)
___________________________________________________________________________________________
|
--•-------•--------------------•---------------------•-------------------•-------------
\ \_ useLayoutEffect \_ user potentially \_ browser paints \_ useEffect
\ executes flushes to DOM executes
\ React flushes to DOM in the useLayoutEffect
That diagram matches my mental model, although I'm not certain that useEffect is guaranteed to fire in the frame that follows the paint as opposed to say, two or three frames later. The timing of effects notes in the React docs only say that it will happen _after_ painting, except if another renders happens first.
Yes, good point about useEffect.
My misconceptions around these things mainly arise from the fact (as I've edited the comment above just when you posted your reply) that I don't know how event loop frames relate to animation frames (setTimeout vs requestAnimationFrame), and what React actually uses to schedule useEffect (I tried looking into React source, but there's an insane amount of abstractions, coupled with Flow, and it's unreadable to me).
FYI, I think #2012 fixes this issue. I tried your text area demo with it and I think it resolved it
Most helpful comment
FYI, I think #2012 fixes this issue. I tried your text area demo with it and I think it resolved it