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.

@bradstevanus1
Copy link

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
Copy link

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
Copy link

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
Copy link

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
Copy link

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
Copy link

amirping commented Feb 19, 2021

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

@fogfish
Copy link

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
Copy link

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
Copy link

dr-skot commented Sep 8, 2021

Just put this in pages/_app.js or some other top-level place:

// suppress useLayoutEffect warnings when running outside a browser
if (!process.browser) React.useLayoutEffect = React.useEffect;

useLayoutEffect and useEffect have the same argument signature, and neither runs if we're not in a browser.
So if we're not in a browser it's safe to globally replace the one that triggers warnings with the one that doesn't.

(If you don't want to rely on the process global, if (typeof window === 'undefined') works too.)


Edit: the above still works, but replacing with () => {} is probably clearer.

@ValeryApps
Copy link

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

@asofiasbo
Copy link

asofiasbo commented Sep 29, 2021

@ValeryApps I'm currently facing the same issue (Ant Design + Next.js). Were you able to get around it?

@bacher
Copy link

bacher commented Oct 1, 2021

I suppose the initial idea is little bit not valid. Warning said that you should not use useLayoutEffect in components what can be rendered on SSR.
Consider situation when we have some component and this component used on two pages.
I open first page via SSR and hydration (useLayoutEffect does nothing on SSR, and make something after hydration),
and I navigate to page 2 on client side (without SSR) and get page 2 layout where our components used, it does something with useLayoutEffect, all is okay.
BUT: My console spammed with tons of warnings. Suppose I read it and replace all useLayoutEffect with useEffect, and what I get?
It will be the same in case of opening page via SSR (no-op on SSR, action of client), but when navigate to page 2, I notice glitch for some time because of using useLayout instead of useLayoutEffect.

Why react forces me to make application worse than it can be?


Long story short: component with useLayoutEffect maybe used not only while SSR, but after client's routing, so the use of the useLayoutEffect should be valid.

@apustula
Copy link

apustula commented Oct 11, 2021

In my case checking typeof window did not help. Instead I have mixed @dhovart and @dr-skot answers which leads me to silence warning.

import { useLayoutEffect } from 'react'

export const useBrowserLayoutEffect = process.browser ? useLayoutEffect : () => {}

@bacher
Copy link

bacher commented Oct 15, 2021

In my case checking typeof window did not help. Instead I have mixed @dhovart and @dr-skot answers which leads me to silence warning.

import { useLayoutEffect } from 'react'

export const useBrowserLayoutEffect = process.browser ? useLayoutEffect : () => {}

As I know, process.browser works only in Next.js.

@evantbyrne
Copy link

evantbyrne commented Oct 17, 2021

I'm using Next.js and simply wrapping useLayoutEffect with a check for window at time of use silenced the warning:

if (typeof window !== "undefined") {
    useLayoutEffect(() => {
        // ...
    });
}

You're not normally supposed to wrap hook calls in conditionals, but because this check always passes on the client and always fails on environments without a DOM I believe it should be fine.

@maranomynet
Copy link

maranomynet commented Nov 17, 2021

It's just amazing that this kludge is still the closest thing to an "official solution" to the noise-pollution problem.

@bacher
Copy link

bacher commented Nov 19, 2021

It's just amazing that this kludge is still the closest thing to an "official solution" to the noise-pollution problem.

I think it's shame... I like React but this decision is beyond common sense.

@mrkan-alelgn
Copy link

mrkan-alelgn commented Dec 22, 2021

@ValeryApps @asofiasbo I'm also having the same issue. did you manage to fix it?

@asofiasbo
Copy link

asofiasbo commented Jan 2, 2022

I haven't. I gave up on using Ant Design alongside NextJS @mrkan-alelgn

@Jackeriss
Copy link

Jackeriss commented Feb 3, 2022

@mrkan-alelgn @ValeryApps
I found the source of this issue in Ant Design issues. I guess we have to wait a bit :)

ant-design/ant-design#30396

@cha0s
Copy link

cha0s commented Mar 20, 2022

Please add an option to suppress this warning.

@johncmunson
Copy link

johncmunson commented Apr 21, 2022

A more robust version written in typescript

export function UseLayoutEffectParent(props: {
  children: ReactNode
  placeholder?: ReactNode
}) {
  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 props.placeholder ? <>{props.placeholder}</> : null
  }

  return <>{props.children}</>
}

@dr-skot
Copy link

dr-skot commented May 1, 2022

The problem with some of the solutions here is they require changing your actual code. You have to replace all your useLayoutEffect calls with a custom useBrowserLayoutEffect—or worse, wrap them all in if statements! which is specifically forbidden. You’re kludging perfectly correct code throughout your codebase, for the sole purpose of suppressing a bogus console warning.

And if the offending code is in an imported module, you can't kludge it.

This solution is one line at the top level of your project and you’re done. Note that it changes nothing that’s actually executed. It replaces a function that never runs with a function that does nothing.

// suppress useLayoutEffect (and its warnings) when not running in a browser
if (typeof window === "undefined") React.useLayoutEffect = () => {};

On the server side, useLayoutEffect is never executed but generates a warning. To suppress the warning, replace the never-executed function with noop. On the client side, this replacement doesn't happen, and all your plain old useLayoutEffects run normally, unkludged and unconditional the way God intended.

@cha0s
Copy link

cha0s commented May 2, 2022

the way God intended

I didn't come from no monkey(patch)! ;)

I actually think creating an explicit new hook to paper over the lack of error suppression is a better approach than hiding it behind a monkeypatch -- your code works great until you port it to an environment that doesn't patch React...

@dr-skot
Copy link

dr-skot commented May 2, 2022

@cha0s maybe you're right. I'm not aware of an environment where React isn't patchable. I do see your point about making things explicit. But in this case monkeypatching away a method that does literally nothing but pollute the console with unhelpful warnings seems less offensive to me than refactoring the whole codebase to accomplish the same thing. Especially if some of the warnings are coming from imported modules I can't refactor.

And if a future React makes the workaround unnecessary, there's only one line of code to delete.

@tenorok
Copy link

tenorok commented May 24, 2022

Why do we need a warning about useLayoutEffect() when we haven't warning about useEffect()? Does anyone expect that useLayoutEffect() will do something on the server?

@dr-skot
Copy link

dr-skot commented May 24, 2022

@tenorok The reason is that useLayoutEffect runs before the DOM is first rendered, whereas useEffect runs after.

With useEffect, the first render on the client is going to be consistent with the server-side render. Because the server doesn't run useEffect, and the client doesn't run it until after the first render.

But useLayoutEffect runs before the client's first render, and the server doesn't run it at all. So there might be a mismatch between the server's render and the client's first render. That's what the warning is telling us.

(Edit: of course, to be technically precise, it's not the functions useEffect and useLayoutEffect, it's the callbacks passed to them, that run at different points in the component lifecycle).

That's the reasoning, but this distinction is really splitting hairs. If useEffect changes the view (without waiting for some sort of promise to resolve) the update probably happens before the user can see the first render. And if useLayoutEffect waits for a promise, the server's render will display in the meantime.

So yeah, warning about one and not the other is highly questionable.

@catamphetamine
Copy link

catamphetamine commented Jun 6, 2022

Bad change. No logic. Disagree.

You’re kludging perfectly correct code throughout your codebase, for the sole purpose of suppressing a bogus console warning.

+1

@xav-ie
Copy link

xav-ie commented Jun 29, 2022

I first tried to put this useLayoutEffect into a *.client.js and load that in… Unfortunately, the error still persisted for some reason..? Anyways, I resorted to dynamically loading in the component. Here is my solution:

index.js

import dynamic from "next/dynamic";

const UseLockScroll = dynamic(() => import("../components/UseLockScroll"), {
  ssr: false,
});

const Home: NextPage = () => {
  return (
    <>
      <UseLockScroll/>
      {/* … */}
    </>
  );
};

UseLockScroll.tsx

import { useLayoutEffect } from "react";

export default function useLockScroll() {
  useLayoutEffect(() => {
    // Get original body overflow
    const originalStyle = window.getComputedStyle(document.body).overflow;
    // Prevent scrolling on mount
    document.body.style.overflow = "hidden";
    // Re-enable scrolling when component unmounts
    return () => {
      document.body.style.overflow = originalStyle;
    };
  }, []); // Empty array ensures effect is only run on mount and unmount
  return null;
}

This works for me and gets rid of that pesky warning. Kind of stupid just to get rid of it, but passes all error checks from the runtime and the editor.

@xenobytezero
Copy link

xenobytezero commented Jul 2, 2022

Arrived here cause my console is being polluted with this too. For non SSR WebComponents, all of the recommendation is to use useLayoutEffect to dynamically import the component, but it means I have a ton of these warnings. Need to figure out a way to suppress that works for me.

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