DEV Community

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

Posted on • Updated on

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

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 custom hook 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

Usage:

import { useWindowDimensions } from '@hooks/useWindowDimensions';
...
const { width, height } = useWindowDimensions();
Enter fullscreen mode Exit fullscreen mode

Top comments (4)

Collapse
 
edwindelbosque profile image
Edwin Del Bosque

Not all heroes wear capes! YOU ARE THE MVP MY FRIEND.

Collapse
 
charlymartin profile image
Charly MARTIN

Nice one dude 👏

Collapse
 
javanocollins profile image
Javano Collins

Amazing!

Collapse
 
alexanderaparicio profile image
Alexander Aparicio Meza Roque

Gracias!