useLayoutEffect and the SSR warning
Tags: javascript, react
Published: 2020-11-02 06:34:47

# 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](https://github.com/Setsun/react-transition-components) library in my [Next.js](https://nextjs.org/) 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.

```text
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](https://reactjs.org/docs/hooks-effect.html), 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](https://reactjs.org/docs/hooks-reference.html#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](aa) 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:
```javascript
const useIsomorphicLayoutEffect =
  typeof window !== 'undefined' ? useLayoutEffect : useEffect;
// Then simply use useIsomorphicLayoutEffect in place of useLayoutEffect
```
Read [Alex's Post](https://medium.com/@alexandereardon/uselayouteffect-and-ssr-192986cdcf7a) 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.

```javascript
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:
```javascript
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!*