Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
useLayoutEffect and server rendering

If you use server rendering, keep in mind that neither useLayoutEffect nor useEffect can run until the JavaScript is downloaded.

You might see a warning if you try to useLayoutEffect on the server. Here's two common ways to fix it.

Option 1: Convert to useEffect

If this effect isn't important for first render (i.e. if the UI still looks valid before it runs), then useEffect instead.

function MyComponent() {
  useEffect(() => {
    // ...
  });
}

Like useLayoutEffect, it won't run on the server, but it also won't warn.

Option 2: Lazily show component with useLayoutEffect

If UI looks broken with useEffect but gets fixed by useLayoutEffect, it means that this component doesn't look right until the effect runs. However, that means the server-rendered HTML version of it won't look right until JavaScript loads anyway. So server-rendering it brings no benefit and shows a confusing UI.

To fix this, you can delay showing that component until after the client side JS loads and hydrates the component. To exclude a Child that needs layout effects from the server-rendered HTML, you can render it conditionally:

function Parent() {
  const [showChild, setShowChild] = useState(false);
  
  // Wait until after client-side hydration to show
  useEffect(() => {
    setShowChild(true);
  }, []);
  
  if (!showChild) {
    // You can show some kind of placeholder UI here
    return null;
  }

  return <Child {...props} />;
}

function Child(props) {
  useLayoutEffect(() => {
    // This is where your layout effect logic can be
  });
}

For example, this is handy for jQuery plugins which can be initialized later.


If you have some use case that isn't covered, please report a complete minimal code example here and we'll try to help.

@maranomynet

This comment has been minimized.

Copy link

@maranomynet maranomynet commented Apr 4, 2019

Is there any way to silence the warning?
I have a use case where the layout-effect is only applied when a prop changes away from it's initial (server-rendered) value. So the hypothetical "won't look right" doesn't apply and I'd rather not needlessly refactor my components only to appease an over-eager alarm-system.

@VicJer

This comment has been minimized.

Copy link

@VicJer VicJer commented Apr 15, 2019

Yeah what @maranomynet said. It's so annoying I keep seeing it in my test logs.

@aenciso

This comment has been minimized.

Copy link

@aenciso aenciso commented Apr 24, 2019

Following because I'm getting lots of warnings

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. in ConnectFunction

@cjolowicz

This comment has been minimized.

Copy link

@cjolowicz cjolowicz commented May 8, 2019

The warning useLayoutEffect does nothing on the server is a common problem during testing when using Redux and Jest. The warning is triggered because react-redux uses useLayoutEffect instead of useEffect when window is defined, and Jest defines window by default.

See react-redux/src/components/connectAdvanced.js, lines 35 to 41:

// React currently throws a warning when using useLayoutEffect on the server.
// To get around it, we can conditionally useEffect on the server (no-op) and
// useLayoutEffect in the browser. We need useLayoutEffect because we want
// `connect` to perform sync updates to a ref to save the latest props after
// a render is actually committed to the DOM.
const useIsomorphicLayoutEffect =
  typeof window !== 'undefined' ? useLayoutEffect : useEffect

To fix the warning, select the node test environment, e.g. by adding a @jest-environment docblock to the very top of your test file:

/**
 * @jest-environment node
 */

Or, to select the node environment globally, use this in your package.json:

{
  "name": "my-project",
  "jest": {
    "testEnvironment": "node"
  }
}

The warning about useLayoutEffect should now disappear because window is no longer defined during test execution.

@dimaqq

This comment has been minimized.

Copy link

@dimaqq dimaqq commented May 15, 2019

Out of the box, Create React App only supports overriding these Jest options:

  • collectCoverageFrom
  • coverageReporters
  • coverageThreshold
  • extraGlobals
  • globalSetup
  • globalTeardown
  • resetMocks
  • resetModules
  • snapshotSerializers
  • watchPathIgnorePatterns.

These options in your package.json Jest configuration are not currently supported by Create React App:

  • testEnvironment

If you wish to override other Jest options, you need to eject from the default setup.

Basically, this alarm is broken for CRA from the get go :(

@parttee

This comment has been minimized.

Copy link

@parttee parttee commented Jun 5, 2019

A little bit hackish workaround for the problem if you still require the mocked window or cannot change the testEnvironment. I had to put this in the setup file and it worked.

import React from "react"; 
React.useLayoutEffect = React.useEffect;

This problem might be fixed in react-redux 7.1.0 because it also checks window.document and window.document.createElement

@fnhipster

This comment has been minimized.

Copy link

@fnhipster fnhipster commented Jun 7, 2019

What if the useLayoutEffect is part of a custom hook?

I.e.:

// useCSSTransition.jsx

import { useEffect, useState, useLayoutEffect } from 'react'

export const useCSSTransition = (refEl, isActive, duration) => {

    const [state, setState] = useState(false)

    useLayoutEffect(() => {
        if (refEl.current === null) return
        refEl.current.style.setProperty('--transition-duration', `${duration}ms`)
        refEl.current.classList.add('css-transition')
    })

    useEffect(() => {
        const timer = setTimeout(() => {
            setState(isActive)
        }, isActive ? 0 : duration)

        if (refEl.current) {
            refEl.current.classList.toggle('css-transition--active', isActive)
        }

        return () => {
            clearTimeout(timer)
        }
    }, [isActive, state])

    return state
}
// SomeComponent.jsx

import React, { useRef } from 'react'
import { useCSSTransition } from './useCSSTransition'

export const SomeComponent = () => {
    const contentEl = useRef(null)
    const contentTransition = useCSSTransition(contentEl, isOpen, 250)

    return (
       <div>
           {contentTransition && (
               <div ref={contentEl}>Hello</div>
           )}
       </div>
    )
}

It seems like you would need to add a new State to SomeComponent based on the value of contentTransition to go around this warning.

@perkinss

This comment has been minimized.

Copy link

@perkinss perkinss commented Jun 10, 2019

...

To fix the warning, select the node test environment, e.g. by adding a @jest-environment docblock to the very top of your test file:

/**
 * @jest-environment node
 */

Or, to select the node environment globally, use this in your package.json:

{
  "name": "my-project",
  "jest": {
    "testEnvironment": "node"
  }
}

The warning about useLayoutEffect should now disappear because window is no longer defined during test execution.

The doc block doesn't work for me but the testEnvironment entry in the package.json does.

@yoyo837

This comment has been minimized.

Copy link

@yoyo837 yoyo837 commented Jun 19, 2019

I get this warning when i render a class component with react-redux like this:

  convertNodeToString = any => {
    if (ReactIs.isElement(any)) {
      try {
        // redux Provider包装避免需要用到store数据的组件,比如会员服务的DataContent
        return ReactDOMServer.renderToStaticMarkup(<Provider store={store}>{any}</Provider>)
          .replace(markupReg, '')
          .trim();
      } catch (error) {
        // eslint-disable-next-line no-console
        console.warn(error);
        return any.toString();
      }
    }
    return any;
  };
@jtomaszewski

This comment has been minimized.

Copy link

@jtomaszewski jtomaszewski commented Jul 3, 2019

I have a use case in which I need to detect the browser's viewport dimensions and basing on it, rerender the component if needed.

I'd like to do it synchronously just after the component is mounted (in class component, I'd do it with componentDidMount). This is because desktop user shouldn't see mobile version of that component and vice-versa.

How can I do it with hooks?

I don't want to delay it to an asynchronous effect (as useEffect would do).
Using useLayoutEffect does the job correctly but it raises a lot of warnings in our app on SSR.

Shouldn't there be a way to silence this warning @gaearon?

@dimaqq

This comment has been minimized.

Copy link

@dimaqq dimaqq commented Jul 3, 2019

@jtomaszewski I assume your useLayoutEffect doesn't actually do anything server-side, does it?
After all I imagine you don't know the viewport size on the server, or do you infer that from user agent?
In that case the warning is quite correct Warning: useLayoutEffect does nothing on the server.
I feel the real question is how to re-run your viewport resize code when SSR html is rehydrated, isn't it?

@jtomaszewski

This comment has been minimized.

Copy link

@jtomaszewski jtomaszewski commented Jul 3, 2019

I assume your useLayoutEffect doesn't actually do anything server-side, does it?

Yes, it doesn't. It works only on client-side.

After all I imagine you don't know the viewport size on the server, or do you infer that from user agent?
In that case the warning is quite correct Warning: useLayoutEffect does nothing on the server.

Yeah, at least in my case, I'm not able to guess the viewport size from the server side.

I feel the real question is how to re-run your viewport resize code when SSR html is rehydrated, isn't it?

Yes, I'd like to run it as soon as possible after the SSR hydration completes (= after the component is mounted).

@MHwishes

This comment has been minimized.

Copy link

@MHwishes MHwishes commented Jul 29, 2019

@perkinss, the same with me. it doesn't work, too

@perkinss

This comment has been minimized.

Copy link

@perkinss perkinss commented Jul 29, 2019

@MHwishes have you found a way to fix it? I had it working with the setting node but the default jest testEnvironment is 'jsdom, and after adding react-apexcharts to my project, I had to switch it back to jsdom to get the tests to pass. They were failing with


    TypeError: Cannot read property 'document' of undefined

      at node_modules/apexcharts/dist/apexcharts.common.js:20255:27
      at Object.<anonymous> (node_modules/apexcharts/dist/apexcharts.common.js:20261:3)
      at Object.<anonymous> (node_modules/react-apexcharts/dist/react-apexcharts.min.js:1:74```
@einsiol

This comment has been minimized.

Copy link

@einsiol einsiol commented Jul 31, 2019

Does anybody know how to silence this warning when running tests on Mocha?

@perkinss

This comment has been minimized.

Copy link

@perkinss perkinss commented Jul 31, 2019

@MHwishes, @einsiol, I managed to silence them again by following the 'hack-ish' suggestion from @parttee above and creating a setup script for tests that sets the useLayoutEffect to be the useEffect:

React.useLayoutEffect = React.useEffect;
@mytheshow

This comment has been minimized.

Copy link

@mytheshow mytheshow commented Aug 28, 2019

I get this warning when i render a class component with react-redux like this:

  convertNodeToString = any => {
    if (ReactIs.isElement(any)) {
      try {
        // redux Provider包装避免需要用到store数据的组件,比如会员服务的DataContent
        return ReactDOMServer.renderToStaticMarkup(<Provider store={store}>{any}</Provider>)
          .replace(markupReg, '')
          .trim();
      } catch (error) {
        // eslint-disable-next-line no-console
        console.warn(error);
        return any.toString();
      }
    }
    return any;
  };

请问你解决了吗?类组件怎么消除这个警告?(Have you solved this warning?)

@jiucheng-front

This comment has been minimized.

Copy link

@jiucheng-front jiucheng-front commented Sep 5, 2019

Hi, I did not use useLayoutEffect any where, just use useEffect ,but still has the warning:
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.

how to fix this ?

@streamich

This comment has been minimized.

Copy link

@streamich streamich commented Sep 19, 2019

FWIW, somebody may find useful useIsomorphicLayoutEffect.

@amrita-syn

This comment has been minimized.

Copy link

@amrita-syn amrita-syn commented Sep 25, 2019

The 'hackish' workaround worked for me. Had to add it to the setupTests.js file. Didn't work in the individual test file.

@rasenplanscher

This comment has been minimized.

Copy link

@rasenplanscher rasenplanscher commented Oct 2, 2019

Personally, I think that the advice to use useEffect seems counter-productive because it directly opposes the advice in the docs to use useLayoutEffect for specific cases.

useIsomorphicLayoutEffect as an idea seems more resaonable, although I would rather remove the useLayoutEffect completely instead of replacing it by useEffect. I think that should, for most cases, result in the desired look. Haven't thoroughly tested that hypothesis yet, but I'm pretty confident of it.

@Maiti

This comment has been minimized.

Copy link

@Maiti Maiti commented Oct 10, 2019

I get this warning when i render a class component with react-redux like this:

  convertNodeToString = any => {
    if (ReactIs.isElement(any)) {
      try {
        // redux Provider包装避免需要用到store数据的组件,比如会员服务的DataContent
        return ReactDOMServer.renderToStaticMarkup(<Provider store={store}>{any}</Provider>)
          .replace(markupReg, '')
          .trim();
      } catch (error) {
        // eslint-disable-next-line no-console
        console.warn(error);
        return any.toString();
      }
    }
    return any;
  };

请问你解决了吗?类组件怎么消除这个警告?(Have you solved this warning?)

you can change your components
remove { connect } from react-redux and use props from import store
like this

const { myState } = store.getState();
const { variable } = myState;

and remove <Provider store={store}> from ReactDOMServer.renderToStaticMarkup

@aleclarson

This comment has been minimized.

Copy link

@aleclarson aleclarson commented Nov 10, 2019

I published react-layout-effect for library authors to share with each other.

If you maintain a popular library and decide to use it, let me know and I'll add you as a maintainer. 👍

@mattgperry

This comment has been minimized.

Copy link

@mattgperry mattgperry commented Feb 24, 2020

If UI looks broken with useEffect but gets fixed by useLayoutEffect, it means that this component doesn't look right until the effect runs. However, that means the server-rendered HTML version of it won't look right until JavaScript loads anyway.

This isn't universally true. SSR runs an application's initial render. Hooks can't run conditionally. So there are instances where a component might want to run useLayoutEffect, but the thing it does to make a component "look right" is conditional on this being a subsequent render.

@corysimmons

This comment has been minimized.

Copy link

@corysimmons corysimmons commented May 23, 2020

Just if (typeof window === 'undefined') return WhateverYouWantYourComponentToReceive

In my case if (typeof window === 'undefined') return [] because in my component I'm looping over some data.

I put this at the top of everything so if it's server-side then I can bail early.

@sayjeyhi

This comment has been minimized.

Copy link

@sayjeyhi sayjeyhi commented May 26, 2020

useIsoMorphicLayoutEffect.js

import { useEffect, useLayoutEffect } from 'react';

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

export default useIsomorphicLayoutEffect;
@corysimmons

This comment has been minimized.

Copy link

@corysimmons corysimmons commented May 26, 2020

^ 🔥

@bradstevanus1

This comment has been minimized.

Copy link

@bradstevanus1 bradstevanus1 commented Jun 3, 2020

@Maiti Could you explain more of your solution to this problem? I'm currently trying to do a similar thing where I check if a component will render null (where the component usually needs Redux state). This produces the useLayoutEffect warning for every component I pass to it, although everything is working.

export function isRenderingNull(componentInstance) { return !renderToStaticMarkup( <Provider store={store}>{componentInstance}</Provider> ); }

@gmattie

This comment has been minimized.

Copy link

@gmattie gmattie commented Jun 6, 2020

Is there any progress being made on this issue?

Like others above, I, too, am passing a <Provider /> wrapped component to ReactDOMServer.renderToStaticMarkup() on the client, which is flooding my console with irrelevant warnings that I'm unable to suppress.

I would like to echo the sentiments of @maranomynet:

I'd rather not needlessly refactor my components only to appease an over-eager alarm-system.

@bradstevanus1

This comment has been minimized.

Copy link

@bradstevanus1 bradstevanus1 commented Jun 6, 2020

@gmattie I ended up disabling the error message by using the code from the last post here: reduxjs/react-redux#1373. I used it in my main app.

@Basovs

This comment has been minimized.

Copy link

@Basovs Basovs commented Aug 9, 2020

Option 1. Fixed my problem. I wnt deep into the 'node-modules' and found that affected component and replaced 'useLayoutEffect' to 'useEffect'. - NO MORE ERRORs in console :) Hope i wont find any buggs caused from this fix. :)

@gmattie

This comment has been minimized.

Copy link

@gmattie gmattie commented Aug 9, 2020

Editing the code ourselves requires the patch every time we update React. Another option, as @bradstevanus1 mentioned, is to suppress that specific error message from being logged.

/*
 * @description Suppresses specific messages from being logged in the Console.
 * 
 * @param {string} message - The target message to suppress, either full text, partial text or a regular expression pattern and case-insensitive.
 * @param {string} method - The Console method of the message to suppress, including "error", "info", "log" and "warn". 
 * @public
 * @function
 * 
 * @example
 * 
 *      suppressConsoleMessage("overeager alarm system", "error");
 * 
 *      console.error("An alarm system for a nuclear power plant")  // <-- Logged
 *      console.error("An overeager alarm system for React")        // <-- Not Logged
 *      console.log("An overeager alarm system for React")          // <-- Logged
 * 
 */
const suppressConsoleMessage = (message, method) => {
    const nativeConsoleMethod = console[method];
    console[method] = (nativeMessage) => {
        if (!RegExp(message, "gi").test(nativeMessage)) {
            nativeConsoleMethod(nativeMessage);
        }
    };
};

//~

suppressConsoleMessage("useLayoutEffect does nothing on the server", "error");
@errnesto

This comment has been minimized.

Copy link

@errnesto errnesto commented Sep 9, 2020

I have the same use case @maranomynet
Inital render looks good from server
useLayoutEffect only runs after user action
but then I need layout effect with useEffect I get a ugly flicker

would be nice if I could just silence this… for now I use the useIsoMorphicLayoutEffect hack from above :-(

@dhovart

This comment has been minimized.

Copy link

@dhovart dhovart commented Dec 23, 2020

@sayjeyhi Somehow it seems more correct to me to write:

import { useLayoutEffect } from 'react';

export const useBrowserLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : () => {};

if you wish to silence the warnings.

Your hook name is misleading, I wouldn't call that isomorphic.

(Also I agree with the first comment)

@amirping

This comment has been minimized.

Copy link

@amirping amirping commented Feb 19, 2021

useLayoutEffect(() => {
    setTimout(() => {
     // logic
    },0)
  });
@fogfish

This comment has been minimized.

Copy link

@fogfish fogfish commented Mar 20, 2021

Everyone is discussing this issue in context of testing. I am seeing this issue due to integrations with non-react library (e.g. leaflet). I'd like to use IconButton and Avatar from material UI as marker on the map. The only solution from leaflet perspective to use DivIcon but it requires to render React Elements to string

const html = new L.DivIcon({
    className: 'my-div-ico',
    html: ReactDOMServer.renderToString(
       <IconButton>
          <Avatar>My</Avatar>
       </IconButton>
    ),
    ...
  })

Are there any suggestions about eliminating the warning in this case? None of proposals discussed in this thread do not work.

@fightant1w1ll

This comment has been minimized.

Copy link

@fightant1w1ll fightant1w1ll commented Jul 5, 2021

Why only warn when useLayoutEffect is used, not when useEffect is used? They both will not run in server side.

@dr-skot

This comment has been minimized.

Copy link

@dr-skot dr-skot commented Sep 8, 2021

if (!process.browser) React.useLayoutEffect = React.useEffect;

@ValeryApps

This comment has been minimized.

Copy link

@ValeryApps ValeryApps commented Sep 13, 2021

Ant Design of React is the cause of this warning in my case. I got the warning when I used import { Menu } from 'antd' and the warning was gone when I commented out the component

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment