I will preface this by saying that everything works correctly, it's just that I'm trying to understand what is happening exactly and why I'm seeing this behavior.
Say I have a component, FooComponent, and two different routes, /foo and /bar. I'm rendering them as follows:
<Route
exact
path="/foo"
render={({ location }) => <FooComponent location={location} />}
/>
<Route
path="/bar"
render={({ location }) => <FooComponent location={location} />}
/>
When clicking between pages /foo and /bar, it seems that FooComponent mounts only once. To test this, here's how the FooComponent is implemented:
const FooComponent = ({ location }) => {
useEffect(() => {
console.log("Mount");
return () => {
console.log("Cleanup");
};
}, []);
return <div>At {location.pathname}</div>;
};
Mount is only logged once.
This completely puzzles me as FooComponent is rendered by two completely different parent Route components, and I thought React would only perform reconciliation on same parent component re-rendering with different child elements. Moreover, changing one to return <div><FooComponent location={location} /></div> and leaving the other as-is will cause the component to unmount.
Here is the reproducible example.
Like what you mentioned, I believe this behavior is mostly related to React Reconciliation process. Whenever the root elements have different types, in our case <div /> vs <FooComponent />, React will create a new tree. That's why we are seeing the unmount event.
In the following case, the prop (location) for FooComponent will be changed when we go to a different route. Because we have two React DOM elements of the same type, React will only update and re-render the component.
// location is changed by react-route
<Route
exact
path="/foo"
render={({ location }) => <FooComponent location={location} />}
/>
<Route
path="/bar"
render={({ location }) => <FooComponent location={location} />}
/>
const FooComponent = ({ location }) => {
useEffect(() => {
console.log("Mount");
return () => {
console.log("Cleanup");
};
}, []);
// the parent components is the same, only the child node of the following div is chnaged, so React rerenders the div.
return <div>At {location.pathname}</div>;
};
On the react-router side, Route doesn't change the DOM structure, if you look at the source code. it only acts as a context provider.
return (
<RouterContext.Provider value={props}>
{props.match
? children
? typeof children === "function"
? __DEV__
? evalChildrenDev(children, props, this.props.path)
: children(props)
: children
: component
? React.createElement(component, props)
: render
? render(props)
: null
: typeof children === "function"
? __DEV__
? evalChildrenDev(children, props, this.props.path)
: children(props)
: null}
</RouterContext.Provider>
);
References:
react-dom/Router.js
React/Reconciliation
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With