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

23 Aug.,2022

 

Window Opening Pole

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:

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