Unexpected uses of useLayoutEffect
Going from animation libraries to galaxy brain state hack
The docs describe useLayoutEffect as a DOM targeted escape hatch for visual updates to the UI. Why then is a hook like this showing up in:
- Compatibility shims for
- Reference implementation for the proposed
- In the react-redux useSelector implementation (before being moved over to useSyncExternalStore).
My answer is that there seems to be an emergent use case of
useLayoutEffect, one that takes advantage of when it happens to run the React update process. I'd describe it as a lifecycle hook for other hooks. To understand why, let's look at the original use case for
Original Superpower: Run Before Browser Paint
Here is the ordering for when React updates the DOM and when effects run.
React explicitly yields the main thread to let the browser paint the frame. This is a departure from the old component lifecycle
componentDidMount but it is in most cases what you want and more performant for synchronization effects that don't immediately have visual updates.
When you do have visual updates, however, doing them in the
useEffect can cause flicker in the UI.
useLayoutEffect offers an escape hatch for this:
This is the original use case and animation libraries like Framer Motion make use of it for performant animations.
New Superpower: Run before useEffect
There is another important property of
useLayoutEffect, however: It runs before
Why is this important? It's for the use case of setting up non-rendering infrastructure across components. Effects and event handlers need to operate on the latest references, for example, but updating them in the render body is impure and
useEffect may be too late. Although hooks have explicit execution order within a component, there is no guarantee in React that a particular component will update before any other.
I did not write any of these examples but here is my stab at why
useLayoutEffect was used in:
useSelectorimplementation: Essentially 'initializes' the selector by updating references for event handlers/effects, creating the subscription and evaluating it again on the update.
useEventcompatibility shim: The updates references that the stable
useEventpoints to before the possibility of event handlers or effects using stale references. It is pointed out that this is not a complete solution, though, because other
useLayoutEffectcalls could still reference stale references
useSyncExternalStore: The code comment above the
useLayoutEffectusage explicitly mentions it needs to saving something for a check that happens in
I believe this usage of
useLayoutEffect is indicative of the need for a new injection point in the React update process, which is being addressed via things like
useEvent. I believe such usage will fade away as React develops appropriate abstractions but it's interesting to think back on how emergent practices in the community get turned into new features.