DEV Community

loading...
Cover image for Creating a custom React hook to get the window's dimensions in Next.js

Creating a custom React hook to get the window's dimensions in Next.js

adrien profile image Adrien Rahier ・2 min read

While working on the Front End of a React App, it's likely that at some point you will need to access the window's dimension.

The classic implementation

To keep your code DRY a general good practice is to externalise this operation to a custom React hook.
Something like this:

// useWindowDimension.js
const [width, setWidth]   = useState(window.innerWidth);
const [height, setHeight] = useState(window.innerHeight);
const updateDimensions = () => {
    setWidth(window.innerWidth);
    setHeight(window.innerHeight);
}
useEffect(() => {
    window.addEventListener("resize", updateDimensions);
    return () => window.removeEventListener("resize", updateDimensions);
}, []);

return { width, height};
Enter fullscreen mode Exit fullscreen mode

While everything works fine in this traditional client-side apps built with React (like create-react-app) problems arise in Gatsby or Next.js.

The SSR inferno

The main problem with Next and Gatsby is that they run the code both on the FE and on the BE... Where window is obviously not defined.
So, how to get around this I hear you ask?

Well, you could do write something like this, where you check if the window is defined or not before continuing.

// useWindowDimension.js
import { useState, useEffect } from 'react';

export default function useWindowDimensions() {

  const hasWindow = typeof window !== 'undefined';

  function getWindowDimensions() {
    const width = hasWindow ? window.innerWidth : null;
    const height = hasWindow ? window.innerHeight : null;
    return {
      width,
      height,
    };
  }

  const [windowDimensions, setWindowDimensions] = useState(getWindowDimensions());

  useEffect(() => {
    if (hasWindow) {
      function handleResize() {
        setWindowDimensions(getWindowDimensions());
      }

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

  return windowDimensions;
}
Enter fullscreen mode Exit fullscreen mode

Note that at this time of writing this is currently the highest voted answer on Stackoverflow regarding Next.js implementation.
However, trying this code out will trigger a warning in Next:
Warning next

So, why is this code flawed and how can we make it bullet-proof?

The solution

It's only after reading Josh's W Comeau that I got a sense of the problem. With the implementation above we are actually bypassing the Rehydration process by checking if the window object is defined or not!
A better implementation would be to actually make sure that the component has mounted (and use the useEffect hook).

The final code looks like this, and everybody is happy!

/**
 * // useWindowDimension.ts
 * * This hook returns the viewport/window height and width
 */

import { useEffect, useState } from 'react';

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

const useWindowDimensions = (): WindowDimentions => {
    const [windowDimensions, setWindowDimensions] = useState<WindowDimentions>({
        width: undefined,
        height: undefined,
    });
    useEffect(() => {
        function handleResize(): void {
            setWindowDimensions({
                width: window.innerWidth,
                height: window.innerHeight,
            });
        }
        handleResize();
        window.addEventListener('resize', handleResize);
        return (): void => window.removeEventListener('resize', handleResize);
    }, []); // Empty array ensures that effect is only run on mount

    return windowDimensions;
};

export default useWindowDimensions;
Enter fullscreen mode Exit fullscreen mode

Discussion (1)

pic
Editor guide
Collapse
charlymartin profile image
Charly MARTIN

Nice one dude 👏