How to detect window size in Next.js SSR using react hook?
Asked Answered
S

9

61

I am building an app using Next.js and react-dates.

I have two component DateRangePicker component and DayPickerRangeController component.

I want to render DateRangePicker when the window's width is bigger than size 1180px, if the size is smaller than this I want to render DayPickerRangeController instead.

Here is the code:

      windowSize > 1180 ?
           <DateRangePicker
             startDatePlaceholderText="Start"
             startDate={startDate}
             startDateId="startDate"
             onDatesChange={handleOnDateChange}
             endDate={endDate}
             endDateId="endDate"
             focusedInput={focus}
             transitionDuration={0}
             onFocusChange={(focusedInput) => {
               if (!focusedInput) {
                 setFocus("startDate")
               } else {
                 setFocus(focusedInput)
                }
               }}
                /> :
             <DayPickerRangeController
               isOutsideRange={day => isInclusivelyBeforeDay(day, moment().add(-1, 'days'))}
               startDate={startDate}
               onDatesChange={handleOnDateChange}
               endDate={endDate}
               focusedInput={focus}
               onFocusChange={(focusedInput) => {
               if (!focusedInput) {
                 setFocus("startDate")
                 } else {
                  setFocus(focusedInput)
                 }
               }}
              /> 
          }

I normally use react hook with window object to detect window screen width like this

But I found that this way is not available when ssr because ssr rendering does not have window object.

Is there an alternative way I can get window size safely regardless of ssr?

Streusel answered 14/8, 2020 at 4:14 Comment(0)
H
132

You can avoid calling your detection function in ssr by adding this code:

// make sure your function is being called in client side only
if (typeof window !== 'undefined') {
  // detect window screen width function
}

full example from your link:

import { useState, useEffect } from 'react';

// Usage
function App() {
  const size = useWindowSize();

  return (
    <div>
      {size.width}px / {size.height}px
    </div>
  );
}

// Hook
function useWindowSize() {
  // Initialize state with undefined width/height so server and client renders match
  // Learn more here: https://joshwcomeau.com/react/the-perils-of-rehydration/
  const [windowSize, setWindowSize] = useState({
    width: undefined,
    height: undefined,
  });

  useEffect(() => {
    // only execute all the code below in client side
    // Handler to call on window resize
    function handleResize() {
      // Set window width/height to state
      setWindowSize({
        width: window.innerWidth,
        height: window.innerHeight,
      });
    }
    
    // Add event listener
    window.addEventListener("resize", handleResize);
     
    // Call handler right away so state gets updated with initial window size
    handleResize();
    
    // Remove event listener on cleanup
    return () => window.removeEventListener("resize", handleResize);
  }, []); // Empty array ensures that effect is only run on mount
  return windowSize;
}

NB: Updated as Sergey Dubovik comment, we dont need to validate windows since useEffect run in client side

Hollister answered 14/8, 2020 at 7:15 Comment(4)
If I'm not mistaken, useEffect only runs on client, so if (typeof window !== 'undefined') condition should not be neededCurkell
If you're writing this in Typescript you might get the TS1251: Function declarations are not allowed inside blocks in strict mode when targeting 'ES3' or 'ES5'. If so, just move the handleResize() function outside the if statementVenerate
If I'm not mistaken, this solutions doesn't work if need the window size on initial page load because width and height are set to undefinedFlocculus
I found that it is necessary to call the setWindowSize directly in useEffect, and not just pass it in as an argument to window.addEventListener. The event listener on "resize" was not sufficient to populate the window dimensions in the initial load of the page.Bromleigh
P
40

While Darryl RN has provided an absolutely correct answer. I'd like to make a small remark: You don't really need to check for the existence of the window object inside useEffect because useEffect only runs client-side and never server-side, and the window object is always available on the client-side.

Platitude answered 12/10, 2020 at 16:54 Comment(3)
I kept seeing this comment about useEffect not running server-side. yet it looked like thats what was happening. Then i noticed that the code i'd used was passing a function into the initial state useState<T>(() => { dosomethingWithWindow}). which was getting called by serverside renderer. So thanks for the reminder that this was the case.Lamrert
This isn't true because nextjs will lint the code and fail to compile with an error about the variable not existing if the undefined case isn't protected againstPurveyance
If this answer didn't work for you, here's another option: https://mcmap.net/q/323912/-referenceerror-window-is-not-defined-on-nextjsStripy
W
7
useEffect(()=> {
   window.addEventListener('resize', ()=> {
       console.log(window.innerHeight, window.innerWidth)
   })
}, [])
Whimsical answered 15/11, 2021 at 2:27 Comment(0)
P
1

For some reason I was getting errors, but this worked for me. Only for width:

const useWidth = () => {
  const [width, setWidth] = useState(0)
  const handleResize = () => setWidth(window.innerWidth)
  useEffect(() => {
      handleResize()
      window.addEventListener('resize', handleResize)
      return () => window.removeEventListener('resize', handleResize)
      // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [])
  return width
}
Panada answered 6/3, 2023 at 8:20 Comment(0)
S
0

here's the solution i'm using: a small npm package found here use-window-size

once installed and imported, all you need to do is use it like this:

const { innerWidth, innerHeight, outerHeight, outerWidth } = useWindowSize();

return (
    <div>Window width: {innerWidth}</div>
)
Stereotropism answered 8/2, 2023 at 10:49 Comment(0)
D
0

You can make use of 'use client' for this page and just treat it as a client side component, which it will be.

Client side components

Then you can avoid all the hacks because of SSR otherwise.

Daumier answered 4/9, 2023 at 5:55 Comment(0)
S
0

I like Darryl RN's solution. If you are facing an issue that size.width is undefined initially when the page loads, just simply initialise the state with window.innerWidth

const [windowSize, setWindowSize] = useState({
  width: window.innerWidth,
  height: window.innerHeight,
});
Stocking answered 3/10, 2023 at 8:56 Comment(0)
B
0

Please pay attention to my answer. Since the solutions above will throw an error and say that the size is not defined or equal to 0, depending on what useState is initialized with at the beginning. And this happens because when the page has already been rendered by the server and any override of components occurs, the hook will show the value undefined. To the extent that this is what is specified in useState. The solution is simple and elegant: just call the function in useEffect. The decision is not mine.

type WindowSize = {
  width: number | undefined;
  height: number | undefined;
};

const useWindowSize = (): WindowSize => {
  const [size, setSize] = useState<WindowSize>({
      width: undefined,
      height: undefined,
  });
  useEffect(() => {
    function handleResize(): void {
      setSize({
        width: window.innerWidth,
        height: window.innerHeight,
      });
    }
    handleResize();
    window.addEventListener("resize", handleResize);
    return (): void => window.removeEventListener("resize", 
  handleResize);
  }, []);

  return size;
};
Brisson answered 13/2 at 8:23 Comment(0)
F
0

My issue with hydration in similar case was related to this:

import { useEffect, useState } from 'react';

type WindowSize = {
  width?: number;
  height?: number;
};

export const useWindowSize = (): WindowSize => {
  const [windowSize, setWindowSize] = useState<WindowSize>({
    width: undefined, // <--- default value is undefined
    height: undefined, // <--- default value is undefined
  });

  useEffect(() => {
    const handleResize = () => {
      setWindowSize({
        width: window.innerWidth,
        height: window.innerHeight,
      });
    };

    window.addEventListener('resize', handleResize);

    handleResize();

    return () => window.removeEventListener('resize', handleResize);
  }, []);

  return windowSize;
};

Default value was undefined and it caused hydration error. When I specified it to be 0 it worked just fine:

import { useEffect, useState } from 'react';

type WindowSize = {
  width: number;
  height: number;
};

export const useWindowSize = (): WindowSize => {
  const [windowSize, setWindowSize] = useState<WindowSize>({
    width: 0, // <--- 0 instead of undefined
    height: 0, // <--- 0 instead of undefined
  });

  useEffect(() => {
    const handleResize = () => {
      setWindowSize({
        width: window.innerWidth,
        height: window.innerHeight,
      });
    };

    window.addEventListener('resize', handleResize);

    handleResize();

    return () => window.removeEventListener('resize', handleResize);
  }, []);

  return windowSize;
};
Frigid answered 17/5 at 16:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.