useLayoutEffect and the SSR warning

useLayoutEffect and the SSR warning

Introduction

Ever saw this dreaded warning in your application logs:

Warning: useLayoutEffect does nothing on the server, because its effect cannot be encoded into the server renderer's output format. This will lead to a mismatch between the initial, non-hydrated UI and the intended UI. To avoid this, useLayoutEffect should only be used in components that render exclusively on the client

I recently saw this while using the react-transition-components library in my Next.js application. Let's take a brief moment to understand what's the difference between useEffect and useLayoutEffect and what this warning means, then we can jump to a couple of simple tricks to avoid this warning.

Tip: Always treat warnings as errors, they are there for a reason! 
Get rid of them as soon as you see them.

useEffect and useLayoutEffect

React developers have gone to a great length to explain Using the Effect Hook, so we'll just try to focus here on the core difference between the two APIs and what the warning means in this context.

The key phrase here from the useEffect documentation is:

The function passed to the useEffect will run after the render is committed to the screen.

Not much to explain here!

The equivalent section for useLayoutEffect says:

it fires synchronously after all DOM mutations. Use this to read layout from the DOM and synchronously re-render. Updates scheduled inside useLayoutEffect will be flushed synchronously, before the browser has a chance to paint.

This means that the function passed to the useLayoutEffect will be executed after changes have been updated to the DOM, but before those changes get painted on the browser screen. Thus you have a chance to read any DOM element's attributes such as position and size and cause a re-render again before browser gets a chance to update the screen. This ensures that your users don't see a glitch of things changing position/size determined by your layout-effect function.

The warning

Both useEffect and useLayoutEffect do nothing on SSR, still the warning is only for useLayoutEffect. The reason being that useEffect doesn't concerns the render cycle of the component and won't affect the first render. However, the useLayoutEffect concerns specifically with rendering and is intended to do stuff that would affect the first render (i.e. what user sees on the screen). Thus the warning that using useLayoutEffect on SSR will cause a mismatch between the intended UI and what you get from the server. Or, in simple terms, your users will see the glitch that you wanted to avoid by using useLayoutEffect

Solutions

The solution for this warning is simple - do what the warning says, don't use useLayoutEffect on SSR! Following are couple of approaches that I found useful:

1. use useIsomorphicLayoutEffect

You can simply use useEffect or useLayoutEffect depending whether the component is rendering on browser or server:

const useIsomorphicLayoutEffect =
  typeof window !== 'undefined' ? useLayoutEffect : useEffect;
// Then simply use useIsomorphicLayoutEffect in place of useLayoutEffect

Read Alex's Post for more on this approach.

I find this approach useful when I am the author of the component that needs to use useLayoutEffect.

2. use useIsClient

This approach is better suited when I am using a component that uses useLayoutEffect but doesn't applies the first approach.

function useIsClient() {
  const [isClient, setIsClient] = React.useState(false);
  // The following effect will be ignored on server, 
  // but run on the browser to set the flag true
  useEffect(() => setIsClient(true), []);
  return isClient
}

// Then in my parent component:
function ParentComponent() {
  const isClient = useIsClient();
  // render the component only on client
  return <>{isClient && <ComponentThatUsesUseLayoutEffect />}</>
}

3. use renderOnlyOnClient HOC

The second approach doesn't work with class components and adds an ugly condition check in the jsx. So, my preferred approach is to use an HOC which works well with both the class and functional components:

function renderOnlyOnClient(TheComponent) {
  return function ClientOnlyComponent({ children, ...rest }) {
    const isClient = useIsClient(); // Yes, the hook is still useful!
    return isClient ? <TheComponent {...rest}>{children}</TheComponent> : <></>;
  }
}

// Now we can just safe-wrap the component and use it freely
const SafeComponentThatUsesUseLayoutEffect = renderOnlyOnClient(ComponentThatUsesUseLayoutEffect);

Hope this helps!