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