问题
Conditional rendering of components based on window.innerWidth
seems to not work as intended just in the production build of Gatsby based website.
The hook I am using to check the viewport's width, with the additional check for the window global to avoid Gatsby-node production build errors, is the following:
import { useState, useEffect } from 'react'
const useWindowWidth = () => {
const windowGlobal = typeof window !== 'undefined'
if(windowGlobal) {
const [width, setWidth] = useState(window.innerWidth)
useEffect(() => {
const handleResize = () => setWidth(window.innerWidth)
window.addEventListener('resize', handleResize)
return () => {
window.removeEventListener('resize', handleResize)
}
})
return width
}
}
export default useWindowWidth
Then in my actual component I do the following:
IndexPage.Booking = () => {
const windowWidth = useWindowWidth()
return (
<div className="section__booking__wrapper">
{ windowWidth <= mediaQueries.lg && <IndexPage.Cta /> }
<div className="section__booking-bg" style={{ backgroundImage: `url(${bg})` }}>
{ windowWidth > mediaQueries.lg && <IndexPage.Cta /> }
</div>
</div>
)
}
It works as it should in development
but the production build fails to render:
<div className="section__booking-bg" style={{ backgroundImage: `url(${bg})` }}>
When resizing the window below the mediaQueries.lg (1024) it then triggers the actual normal behaviour or conditionally rendering mobile and desktop versions of the component.
To doublecheck if it was because the render triggers on just the resize
event (which it doesn't as it works on load in development
environment) I also simply, from within the hook console.log()
the return value and it gets printed, in production correctly on load.
There are also no errors or warnings in the production or development
build whatsoever.
Edit as per @Phillip 's suggestion
const useWindowWidth = () => {
const isBrowser = typeof window !== 'undefined'
const [width, setWidth] = useState(isBrowser ? window.innerWidth : 0)
useEffect(() => {
if (!isBrowser) return false
const handleResize = () => setWidth(window.innerWidth)
window.addEventListener('resize', handleResize)
return () => {
window.removeEventListener('resize', handleResize)
}
})
return width
}
It now works just when you resize it, once, under the mediaQueries.lg threshold and then it works flawlessly across desktop and mobile but not on load.
回答1:
I had a similar problem to this and haven't found a solution, but a work around. Put the following at the start of your render:
if (typeof window === `undefined`) {
return(<></>);
}
What I think is happening is that Gatsby is building the page with a style based off the window width (which will be 0 / undefined). Then it's not updating the style in the DOM once the page loads as it thinks it has already performed that action. I think this is a small bug in Gatsby maybe?
Either way, the above renders your component blank during the build, forcing it to fully respect all logic when the page loads. Hopefully that provides a solution albeit not a satisfying/complete explanation :)
回答2:
Don’t call Hooks inside loops, conditions, or nested functions (from React docs)
React Hooks must run in the exact same order on every render. Move your condition into the useEffect
callback:
useEffect(() => {
if (typeof window === 'undefined') return;
const handleResize = () => setWidth(window.innerWidth);
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize)
};
});
回答3:
I'm guessing it is too late to answer but calling handleResize before adding the event listener should work. Here is a code I used for same purpose:
useEffect(() => {
setWidth(window.innerWidth);
window.addEventListener("resize", () => {
setWidth(window.innerWidth);
});
return () => {
window.removeEventListener("resize", () => {});
};
}, []);
来源:https://stackoverflow.com/questions/58608523/gatsby-react-conditional-rendering-based-on-window-innerwidth-misbehaving