useLayoutEffect and SSR

How to get warning free usage of useLayoutEffect when server-side rendering

Photo by Caleb George on Unsplash

useEffect and useLayoutEffect are React hooks that allow for the creation of side effects. They are a replacement for the componentDidMount , componentDidUpdate and componentWillUnmount lifecycle methods on class components.

Kent C. Dodds has put together a great guide on when to use useEffect and when to use useLayoutEffect. TLDR: most of the time you will want useEffect

useLayoutEffect is put forward as the safest upgrade path for class components

useLayoutEffect fires in the same phase as componentDidMount and componentDidUpdate — React Documentation

However, if you use useLayoutEffect then you will get a nasty SSR console warning

function App() {

useLayoutEffect(() => {

console.log("layout effect");

});

return "hello world";

} // Will log a warning

const html = ReactDOMServer.renderToString(<App />);

⚠️ 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. See https://fb.me/react-uselayouteffect-ssr for common fixes.

Even though useLayoutEffect does nothing when using renderToString it will log a big old good time warning. Personally, I am not a fan of this behaviour given that useLayoutEffect is the safest upgrade path for class components. However, I can see the argument that for most people they should be using useEffect and this pushes them in that direction. But I still think logging a warning is too much.

A workaround (hack)

Create useIsomorphicLayoutEffect

// use-isomorphic-layout-effect.js import { useLayoutEffect, useEffect } from 'react'; const useIsomorphicLayoutEffect =

typeof window !== 'undefined' ? useLayoutEffect : useEffect; export default useIsomorphicLayoutEffect;

Use instead of useLayoutEffect

// app.js

import useLayoutEffect from './use-isomorphic-layout-effect'; function App() {

useLayoutEffect(() => {

console.log('hello there');

}, []);

return 'Hello world';

};

No more SSR warnings! 😊

In this technique (hack), we conditionally return useLayoutEffect or useEffect based on whether we are running in the browser or not. Both useEffect and useLayoutEffect have the same API so your type system should be all good.

This hack is currently being used in react-redux and react-beautiful-dnd .

eslint

eslint-plugin-react-hooks is a really valuable eslint plugin if you are using hooks. In order for our custom use-isomorphic-layout-effect to correctly leverage the rules that the plugin has for useLayoutEffect , your import must be named useLayoutEffect . You can either do this by using a named export in use-isomorphic-layout-effect

import { useLayoutEffect, useEffect } from 'react'; const useIsomorphicLayoutEffect =

typeof window !== 'undefined' ? useLayoutEffect : useEffect; // Ensure the name used in components is useLayoutEffect

export default { useLayoutEffect: useIsomorphicLayoutEffect };

Or you can use eslint to force the name of the default export from use-isomorphic-layout-effect to be useLayoutEffect .

module.exports = {

// other config ... rules: {

'no-restricted-imports': [

'error',

{

// Disabling using of useLayoutEffect from react

{

name: 'react',

importNames: ['useLayoutEffect'],

message:

'`useLayoutEffect` causes a warning in SSR. Use `useIsomorphicLayoutEffect`',

},

],

},

],

'no-restricted-syntax': [

'error',

// Ensure import from '*use-isomorphic-layout-effect' is `useLayoutEffect` to leverage `eslint-plugin-react-hooks`

{

selector:

'ImportDeclaration[source.value=/use-isomorphic-layout-effect/] > ImportDefaultSpecifier[local.name!="useLayoutEffect"]',

message:

'Must use `useLayoutEffect` as the name of the import from `*use-isomorphic-layout-effect` to leverage `eslint-plugin-react-hooks`',

},

],

}

}

Summary