I'm getting something very odd happening in a Gatsby website I'm building. I feel like it's probably a weird React bug I don't know about. It's so strange I might have trouble describing it but here goes:
I want to render a different Footer component on my Contact page to the rest of my site. The components are similar but not identical.
I'm using code like this:
{
!isContact ? // this is a useState state
<Footer /> :
<FooterContact />
}
The bug is, that sometimes when isContact is true, the html that is rendered consists of the first few lines of Footer component followed by the rest of my FooterContact component. i.e. I get:
<footer> // this is from the Footer component
<div class="inner"> // this is from the Footer component
<h1>Contact footer</h1> // this is from the FooterContact component
<p>Lorem Ipsum</p> // this is from the FooterContact component
</div>
</footer>
The conditions this happens in are:
FooterContact component renders fine.Has anyone experienced anything like this before?
Any suggestions of what I can try to debug this? I've been trying stuff for the last couple of hours and I don't feel like I'm any closer to working out what's going on.
Thanks
This occurs when there is a difference between the server-side rendered HTML and the initial shadow DOM rendered client-side. This causes problems during the hydration process where React is trying to align the DOM output of your component renders with the live HTML DOM.
Look for areas of your code where you are rendering conditionally based on an expression that depends on the environment or rendering context. Specifically, you're looking for any expression that might result in a different conditional branch being rendered during the initial render.
For example, this is a problem:
const ExampleOfProblematicCode = () => {
const isClientSide = typeof window !== "undefined"
return isClientSide ? <div>Hello!</div> : null
}
Instead, wait until a useEffect hook has an opportunity to be executed and change state to prompt a re-render before altering the output client-side:
const ExampleOfGoodPractice = () => {
const [isLoaded, setIsLoaded] = useState(false)
useEffect(() => { setIsLoaded(true) }, [])
return isLoaded ? <div>Hello!</div> : null
}
This will render the same output during the initial render, after which React will run the useEffect hook that results in isLoaded being true and rendering the alternate conditional branch.
Edit: Just to clarify, this behavior is exactly what is expected when there is a hydration mismatch:
The conditions this happens in are:
- It's only happening on a live server.
- It's only happening when I "refresh" the Contact page. If I travel there via a link from another page, the FooterContact component renders fine.
This is because the initial load is the only one that contains pre-rendered HTML. After that, React hydrates and Gatsby handles client-side navigation by loading the required webpack chunks and page-data.json files to render subsequent pages client-side.