Creating a custom React hook to get the window's dimensions in Next.js
Tags: nextjs, react, typescript, hooks
Published: 2021-02-15 14:55:32

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: 

```js
// 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};
```

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.

```js
// 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;
}
```

Note that at this time of writing this is currently the highest voted answer on [Stackoverflow](https://stackoverflow.com/questions/36862334/get-viewport-window-height-in-reactjs/59185109) regarding Next.js implementation.
However, trying this code out will trigger a warning in Next: 
![Warning next](https://dev-to-uploads.s3.amazonaws.com/i/93l8b3p9pjeqwnujswpr.jpg)

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](https://www.joshwcomeau.com/react/the-perils-of-rehydration/) 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!

```ts
/**
 * // 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;
```

Usage:
```ts
import { useWindowDimensions } from '@hooks/useWindowDimensions';
...
const { width, height } = useWindowDimensions();
```