Components being unnecessarily remounted
chiel opened this issue · comments
Hi there, I'm doing a POC for work to see if wouter
is the library we want to move (from react-router
which we have a bunch of issues with).
One issue I'm currently running into, which we have with react router as well in some circumstances, is that elements get re-mounted seemingly unnecessarily.
For example:
<Switch>
<Route path="a">
<ComponentA />
</Route>
<Route path="b">
<ComponentB />
</Route>
</Switch>
Where ComponentA
and ComponentB
both look something like this (the nesting of div > Navbar
is the same between them:
function ComponentA() {
return (
<div>
<Navbar />
<p>Component A</p>
</div>
)
}
And Navbar
looks something like this:
function Navbar() {
useEffect(() => {
return () => {
console.log('NAVBAR UNMOUNT');
}
}, []);
return null;
}
Whenever switching from route /a
to route /b
, the Navbar
gets unmounted, and remounted, seemingly unnecessarily. Usually, React will reconcile the difference between these components and realise that the navbar does not need to be un/re-mounted and just leave it be. I've tried adding key
props at various levels to try help the situation, but to no avail.
Do you have any clues what might be causing this?
Just to confirm, your app is not in StrictMode
, right?
Nope! We are actually still on react 16.8, if that matters.
I see, this looks like a standard behaviour: React sees that <ComponentA />
and <ComponentB />
elements are different and it unmounts the first one and mounts the new one. Can you run the following experiment: replace your switch with a simple state like so:
const [value, setValue] = useState("a")
<>
{value === "a" ? <ComponentA /> : <ComponentB />}
</>
I expanded it a little bit with also adding a toggle in the respective components, and it indeed exhibits the same behaviour. Weirdly enough, in react-router 5 I can do this (but only using the render
prop), but then I can't use hooks in the function passed to the render
prop.
So I'm curious what causes react to reconcile these changes vs when it does not cause it should see that the internal structure of each of these components is partially the same and only make the changes that are absolutely necessary... or so I thought.
Maybe the fact that the render
prop function in react-router is not actually a component but just "a function that returns some react elements" (since you cannot use hooks) is why it works? Unsure.
Maybe the fact that the
render
prop function in react-router is not actually a component but just "a function that returns some react elements" (since you cannot use hooks) is why it works? Unsure.
That’s exactly why. Elements can be compared, components can’t. These two components might have the same JSX output, but they also can have different side-effects or state, so React will have to re-mount it.
I see, alright - so the only way, feasibly, to make this work is to use the render function pattern in wouter? Which means that function would also not support functions since it's a regular function?
In any case I'll go ahead and close this issue since it is expected behaviour - thanks for your help!
Just verified, it indeed works fine with a child render function with the same caveat of not being able to use hooks in that function.