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};
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;
}
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:
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;
Usage:
import { useWindowDimensions } from '@hooks/useWindowDimensions';
...
const { width, height } = useWindowDimensions();
Top comments (5)
Not all heroes wear capes! YOU ARE THE MVP MY FRIEND.
Nice one dude 👏
Amazing!
thanksyou so much bro <3
Gracias!