Best strategy for checking whether (typeof window !== undefined) while using Hooks
See original GitHub issueI am using a custom hook to access breakpoint information, which is using the window
property, so I’m running into trouble when building.
I am calling the hook from a component like so:
const breakpoint = useBreakpoints(typeof window !== undefined)
and here is the code I have for the hook itself:
const getDeviceConfig = (width) => {
if (width >= 320 && width < 375) {
return 'xs';
} else if(width >= 375 && width < 768 ) {
return 'sm';
} else if(width >= 768 && width <= 1024) {
return 'md';
} else if(width > 1024) {
return 'lg';
}
};
const useBreakpoint = (isBrowser) => {
const [brkPnt, setBrkPnt] = useState(() => getDeviceConfig(isBrowser && window.innerWidth));
useEffect(() => {
const calcInnerWidth = () => {
setBrkPnt(getDeviceConfig(isBrowser && window.innerWidth))
};
isBrowser && window.addEventListener('resize', calcInnerWidth);
return () => isBrowser && window.removeEventListener('resize', calcInnerWidth);
}, []);
return brkPnt;
}
export default useBreakpoint
The reason I am passing the (typeof window !== undefined)
value from the components to the hook is because of the “Rules of Hooks”: “Don’t call Hooks inside loops, conditions, or nested functions”.
This works, but results in a very weird behavior on deployed version where the page (on desktop mode) loads to the mobile layout and needs a couple of refreshes to correct itself.
Having done some reading in similar issues and answers I now understand a better way to go about it is to do something like this:
const isBrowser = typeof window !== `undefined`
// Function Component with renturned JSX only
const myComponent = (props) => (
isBrowser && <myActualComponent data={props.data} />
)
but I want to understand exactly what is happening that triggers this weird behavior on deploy?
Issue Analytics
- State:
- Created 3 years ago
- Comments:5 (3 by maintainers)
Top GitHub Comments
(typeof window !== undefined)
is always going to be the same value from the first time it’s encountered(either during SSR or on the client), it’s a global, so no need to pass it in as a value, just include it with your hook where it’s used out of it’s scope and reference it as you do withisBrowser
,gatsby-image
does this.Yes, as I showed in that snippet, but if it only matters for your hook as a condition, you don’t need to wrap it like my example snippet shows there. That was for third-party imported components that were causing problems because they internally use
window
and don’t have support for SSR otherwise.SSR will happen with a minimal viewport afaik, so expect anything that might bake in inline CSS or markup/components based on width to favor mobile breakpoints.
You can also use the
window.matchMedia
API for a listener that does similar to what you’re doing here (I have an active PR doing such withgatsby-image
, but not hooks based), there’s also a packagereact-media
I think that achieves similar if that suits you, can’t recall but it might also have a hook.For the lack of updating/responsiveness in the situation you describe, it can be due to hydration. React hydrates from the SSR html it first loads, and assumes the state it would compute would match the HTML it received, and doesn’t bother to check for a mismatch, so you need to trigger a re-render that would alter the returned JSX if I recall… I have a basic PR here for
gatsby-image
to handle a similar issue.If you instead get a momentary flicker, that’d be from the SSR output before React/JS kicks in, and may require additional handling, I have a PR showing that fix off too here.
That’s a bit odd, since that’s a hydration issue not an SSR one where
isBrowser
guard would work better. Good that it resolved your issue though.