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.
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 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?
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