Next.js 13 "window is not defined"
Asked Answered
R

4

25

I wrote a component that looks like this:

'use client'

export const HorizontalModule = (props: any) => {
    ...

    return (
        {scrollPosition >= 0 && (
            <FirstModule />
          )}

          {scrollPosition >= window.innerHeight * 2 && (
            <SecondModule />
          )}        
    )
})

But I got the "window is not defined" error.

Reading through different posts, I have found that most people found using dynamic importing useful so I did this in the parent component which is a nextjs page:

const HorizontalModule = dynamic<any>(
  () => import('./HorizontalModule/HorizontalModule').then((mod) => mod.HorizontalModule),
  {
    ssr: false,
    suspense: true,
    loading: () => <p>Loading...</p>
  }
)

At first I was getting this error: "Object is not a function"

Now I'm getting "Unsupported Server Component type: undefined"

I don't exactly know what I did to switch the error but it is still not working.

I gotta mention, I use the window object all over the HorizontalModule code, in different useEffects but when I use it inside the render function, all stops working.

I also tried writing inside the component a validation like this:

if (window === undefined) return (<></>)
return (...)

I got the same window undefined object or a hydration error.

I don't know what else is there to do, ssr false doesn't work, suspense either, window condition...

Reddy answered 10/3, 2023 at 3:47 Comment(2)
Try if (typeof window !== "undefined") { ... }Solder
It threw a hydration errorReddy
O
40

From the Next.js 13 docs: https://beta.nextjs.org/docs/rendering/server-and-client-components#client-components

[Client Components] are prerendered on the server and hydrated on the client.

So the 'use client' directive does not render the page entirely on the client. It will still execute the component code on the server just like in Next.js 12 and under. You need to take that into account when using something like window which is not available on the server.

You can't just check if the window is defined and then immediately update on the client either, since that may cause a mismatch between the server prerender and the client initial render (aka. a hydration error).

To update the page on client load, you need to use a useEffect hook combined with a useState hook. Since useEffect is executed during the initial render, the state updates don't take effect until the next render. Hence, the first render matches the prerender - no hydration errors. More info here: https://nextjs.org/docs/messages/react-hydration-error

Instead of creating this mechanism in every component that needs it, you can make a context that simply sets a boolean using useEffect, telling us we are safe to execute client code.

is-client-ctx.jsx

const IsClientCtx = createContext(false);

export const IsClientCtxProvider = ({ children }) => {
  const [isClient, setIsClient] = useState(false);
  useEffect(() => setIsClient(true), []);
  return (
    <IsClientCtx.Provider value={isClient}>{children}</IsClientCtx.Provider>
  );
};

export function useIsClient() {
  return useContext(IsClientCtx);
}

_app.jsx

function MyApp({ Component, pageProps }) {
  return (
    <IsClientCtxProvider>
      <Component {...pageProps} />
    </IsClientCtxProvider>
  );
}

Usage

  const isClient = useIsClient();
  return (
    <>
      {scrollPosition >= 0 && <FirstModule />}

      {isClient && scrollPosition >= window.innerHeight * 2 && <SecondModule />}
    </>
  );

Live Demo: https://stackblitz.com/edit/nextjs-mekkqj?file=pages/index.tsx

Optative answered 10/3, 2023 at 5:32 Comment(6)
Just for posterity, my first working solution was to directly add the window validation right at the start of the component so no code was run if there were no window but it logs a lot of errors to vscode. Using you approach I surrounded my HorizontalModule with IsClientProvider, used the hook normally and only had validate if it was true inside my useEffects and the render. Thank youReddy
Nice work, such a shame next13 doesn't work....Reaction
Getting ReferenceError: createContext is not defined. This worked for me instead: #75369536Finfoot
@Finfoot import { createContext } from 'react'. You can check out the live demo for an example.Optative
@Finfoot although that other answer is useful, it completely disables ssr for that component, ie. the entire component is rendered on the client. This approach lets you render everything you can on the server, then perform updates on the client given a condition.Optative
Thanks @ChrisHamilton in my case it's an audio player component so without SSR is fine. Sorry I missed the demo, I'll try again later with another component.Finfoot
L
4

For rendering some components in the client side you can use dynamic import in Next.js 13ver. with settings option { ssr: false }

You can check on the docs - https://nextjs.org/docs/messages/react-hydration-error#solution-2-disabling-ssr-on-specific-components

import dynamic from 'next/dynamic';
const MyClientComponent = dynamic(
  () => import('@/components/my-client-component/MyClientComponent').then(module => module.MyClientComponent) as any,
  { ssr: false },
) as any;
Lambeth answered 14/2 at 12:48 Comment(0)
F
0

Add this line to wrap your client side function/code:

if (typeof window !== "undefined") { ... }
Frausto answered 3/4 at 15:0 Comment(0)
P
-2
'use client'

import { useEffect, useState } from 'react'

type IUseStickyHeaderProps = {
   active: boolean
}  

export const useStickyHeader = ():IUseStickyHeaderProps => {
    const [active, setActive] = useState(false)

    useEffect(() => {
        window.addEventListener('scroll', () => {
            if (window.scrollY > 70) {
                setActive(true)
            } else {
                setActive(false)
            }
        })
    }, [])

    return {
        active
    }
}

Pulcheria answered 12/6, 2023 at 14:51 Comment(1)
You should add explanation.Griffe

© 2022 - 2024 — McMap. All rights reserved.