51

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?

GoonGamja
  • 1,936
  • 5
  • 20
  • 46

5 Answers5

115

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

Darryl RN
  • 7,432
  • 4
  • 26
  • 46
  • 12
    If I'm not mistaken, useEffect only runs on client, so if (typeof window !== 'undefined') condition should not be needed – Sergey Dubovik May 18 '21 at 12:54
  • 1
    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 statement – matteo_dm May 31 '21 at 20:20
  • 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 undefined – Javano Collins Mar 26 '22 at 11:33
  • 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. – N4v Aug 13 '22 at 16:27
38

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.

  • 1
    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(() => { dosomethingWithWindow}). which was getting called by serverside renderer. So thanks for the reminder that this was the case. – Emile Nov 17 '20 at 14:40
  • 2
    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 against – monokrome Jan 10 '21 at 22:07
  • If this answer didn't work for you, here's another option: https://stackoverflow.com/a/66696801/332578 – Jacksonkr Jun 12 '21 at 17:36
6
useEffect(()=> {
   window.addEventListener('resize', ()=> {
       console.log(window.innerHeight, window.innerWidth)
   })
}, [])
fruitloaf
  • 1,628
  • 15
  • 10
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>
)
0

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
}