Skip to content

Instantly share code, notes, and snippets.

@gaearon
Last active October 22, 2024 04:12
Show Gist options
  • Save gaearon/e7d97cdf38a2907924ea12e4ebdf3c85 to your computer and use it in GitHub Desktop.
Save gaearon/e7d97cdf38a2907924ea12e4ebdf3c85 to your computer and use it in GitHub Desktop.
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.

@AlexIsMaking
Copy link

A better solution is to use an isomorphic effect.

This works great, thanks for sharing!

@nwazuo
Copy link

nwazuo commented Apr 6, 2024

A better solution is to use an isomorphic effect.

hooks.ts:

import { useEffect, useLayoutEffect } from 'react'

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

Component.tsx:

import { useIsomorphicLayoutEffect } from 'hooks.ts'

const Component = () => {
	// Server? useEffect(). Client? useLayoutEffect() ๐ŸŽ‰
	useIsomorphicLayoutEffect(() => {})

	return <p>Cool component</p>
}

If you're going to be using a lot of these hooks, you can also just npm i usehooks-ts.

I use this, works great!

@gmjacob
Copy link

gmjacob commented Apr 15, 2024

A better solution is to use an isomorphic effect.

hooks.ts:

import { useEffect, useLayoutEffect } from 'react'

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

Component.tsx:

import { useIsomorphicLayoutEffect } from 'hooks.ts'

const Component = () => {
	// Server? useEffect(). Client? useLayoutEffect() ๐ŸŽ‰
	useIsomorphicLayoutEffect(() => {})

	return <p>Cool component</p>
}

If you're going to be using a lot of these hooks, you can also just npm i usehooks-ts.

Thank you!

This solved my problem as well ๐Ÿ™

@bryanrasmussen
Copy link

just because useLayoutEffect is called does not mean that the display will not look correct unless useLayoutEffect can run, otherwise if this were the case the whole concept of Progressive enhancement would not make sense.

@sisyl
Copy link

sisyl commented May 16, 2024

What about this :

import { useLayoutEffect } from "react";
import tenoxui from "tenoxui";
import property from "@tenoxui/property";

const App = () => {
  useLayoutEffect(() => {
    // add tenoxui
    tenoxui(property); // use default property
  }, []);
  return <h1 className="tc-red">Hello World!</h1>;
};

export default App;

Code above is a simple react app using a css framework called tenoxui. It using useLayoutEffect to make sure the style will applied immediately ๐Ÿ˜…. It's work fast and perfectly fine on vite + react, I want to implement the same thing on nextjs (I am new on nextjs ๐Ÿ˜ญ). This is my code :

// utils/styler.ts

import { useLayoutEffect } from "react";
import tenoxui, { makeStyles, use } from "tenoxui";
import property from "@tenoxui/property";

export function styler() {
  useLayoutEffect(() => {
    use(property, { c: "color" });
    makeStyles({
      body: "bg-#0d0d0d",
      main: "p-2rem mh-auto w-mx-1440px",
      ".c-primary": "tc-$primary",
      ".flex-center": "d-flex flex-parent-center"
    });
    tenoxui();
  }, []);
}

// components/Main.tsx

import React from "react";
import { styler } from "@/utils/styler";

const Main = () => {
  styler();
  return (
    <main>
      <h1 className="c-primary">It works!</h1>
      <div className="box-100px bg-$primary flex-center br-4px">
        <p className="tx-#0d0d0d fw-500">It works!</p>
      </div>
    </main>
  );
};

export default Main;

// and finally, pages/index.tsx

import Head from "next/head";
import dynamic from "next/dynamic";

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

export default function Home() {
  return (
    <>
      <Head>
        <title>Docs page</title>
        <meta name="description" content="Generated by create next app" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <Main />
    </>
  );
}

It turns out that it works like I want, the styles will just fastly loaded if because I added ssr: false for Main component. So, I still didn't know how to make these css framework tenoxui will applied for all components. Help...

Btw, this is the links for tenoxui resources :

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