useEffect vs useLayoutEffect and server-side rendering
• by • in categories: Culture Amp, react, web development
Today I learned
the difference between useEffect
and useLayoutEffect
in React,
and why the second one prints an ugly warning during server-side rendering.
useEffect(
() => {
/* non-paint-blocking effect */
},
[
/* dependencies */
],
);
useLayoutEffect(
() => {
/* paint-blocking effect */
},
[
/* dependencies */
],
);
useEffect
and useLayoutEffect
are basically
the “async” and “sync” versions of the same hook;
otherwise, they behave identically.
useEffect
runs in the background asynchronously
while the render where its dependencies changed is allowed to render and paint
for the user to see,
whereas useLayoutEffect
blocks the render where the dependencies’ values changed
from being painted to the screen
until after the body of useLayoutEffect
runs
(and presumably tweaks the DOM in some way).
Until today we were using useLayoutEffect
to perform a redirect in a new app:
import { routes } from "routes";
import { useCurrentUser } from "src/context";
import { useLayoutEffect } from "react";
import { useRouter } from "next/router";
export default function Redirect() {
const user = useCurrentUser();
const router = useRouter();
useLayoutEffect(() => {
if (!user) {
return;
}
router.replace(routes.index({ accountId: user?.account_id.toString() }));
}, [user, router]);
}
Since we’re going to redirect the browser anyway,
it makes sense to save it the trouble of
painting the initial render to the screen, right?
Not when there is server-side rendering in the mix!
When a useLayoutEffect
is encountered by React running on the server, it displays an ugly warning:
Warning: useLayoutEffect does nothing on the server, because its effect cannot be encoded into the server renderer’s output format.
In fact, neither useEffect
nor useLayoutEffect
can have any effect
on the server-rendered output that gets sent to the browser
(since there is no actual DOM for these effects to tweak),
but this warning is emitted only for useLayoutEffect
because
it is usually used to do something to the DOM
before the user gets to see the rendered component.
Server-rendering will cause that “untweaked” version of the rendered output
to be sent to the browser and shown to the user
before the client-side app fires up and runs the useLayoutEffect
to tweak it.
So React is warning us that the user may see an ugly/broken version of the UI
that we don’t want them to see.
What’s useLayoutEffect
good for?
Permalink to
What’s useLayoutEffect good for? When I shared this internally at Culture Amp, several people remarked that
they couldn’t think of any real use cases for useLayoutEffect
.
They are indeed rare – more so every year as CSS grows more and more capable –
but here’s a semi-contrived example:
Let’s say you wanted to let the browser lay out
the “natural” height of a div
based on its content,
and then use JavaScript to animate it from zero to that “natural” height,
so it appeared to grow vertically from its top edge,
revealing its content as it went.
You wouldn’t want the user to see a “flash” of the full-height div
before you set its height back to zero for the start of the animation.
useEffect
would display that flash,
whereas useLayoutEffect
would let you inspect the height of the rendered div
and then adjust it before the user got to see it.
In this case, the warning from React would be telling us that
the user would see the flash,
because the full-height div
is getting rendered on the server,
and the height tweak won’t be applied by useLayoutEffect
until the JavaScript starts up on the client –
long after the user got to see the server-rendered version of the page.
The example given in the official docs is also good: it involves calculating the position of a tooltip based on the rendered dimensions of the element to which it is attached.
See also:
Permalink to See also:- A good short video demonstrating the difference between useEffect and useLayoutEffect.
- A good gist that explains the SSR issue and two available workarounds.