For a long time I have been trying to get routing to work in our app after an error boundary has been hit, but only today did I find the code that was seemingly identical to the many examples lying around had one important difference: the routes were wrapped by a Switch
. This simple change is enough to stop routing from working, if enabled. Demo
Take the following snippet. If I remove the Switch
bit, this works fine even if each component should fail, but not if wrapped by the switch. I would like to know why.
<div style={{ backgroundColor: "#ffc993", height: "150px" }}>
<Switch>
<Route
path="/"
exact
render={() => (
<ErrorBoundary>
<MyComponent1 title="Component 1" />
</ErrorBoundary>
)}
/>
<Route
path="/comp1"
render={() => (
<ErrorBoundary>
<MyComponent1 title="Component 1 Again" />
</ErrorBoundary>
)}
/>
<Route
path="/comp2"
render={() => (
<ErrorBoundary>
<MyComponent2 title="Component 2" />
</ErrorBoundary>
)}
/>
</Switch>
Basically, this problem boils down to how React does reconciliation.
Say we have this example app:
This will, somewhat unintuitively, reuse the same instance of
Foo
for both routes! A<Switch>
will always return the first matched element, so basically when React renders this is equivalent of the tree<App><Foo/></App>
for path "a" and<App><Foo/></App>
for path "b". If Foo is a component with state, that means that state is kept, as the instance is just passed new props (for which there are none, exceptchildren
, in our case), and is expected to handle this by recomputing its own state.As our error boundary is being reused, while it has state that has no way of changing, it will never re-render the new children of its parent route.
React has one trick hidden up its sleeve for this, which I have only seen explicitly documented on its blog:
I was first hinted to this by a somewhat related issue on Brian Vaughn's error bondary package:
The alternative to using
key
s would be to implement either exposing some hook that could be called externally or by trying to inspect thechildren
property for change, which is hard. Something like this could work (demo):But it's more work and much more error prone, so why bother: just stick to adding a
key
prop :-)To give you the shortcut of this fix, please see the new "key" prop on each of the ErrorBoundary component and each must be unique, so the code should look like this:
To elaborate, the answer of @oligofren is correct. Those 3 ErrorBoundary components are the same instances but may differ in props. You can verify this by passing "id" prop to each of the ErrorBoundary components.
Now you mentioned why is that if you remove Switch component, it works as expected? Because of this code: https://github.com/ReactTraining/react-router/blob/e81dfa2d01937969ee3f9b1f33c9ddd319f9e091/packages/react-router/modules/Switch.js#L40
I recommend you to read the official documentation of React.cloneElement here: https://reactjs.org/docs/react-api.html#cloneelement
I hope this gives you an idea on this issue. Credit to @oligofren as he explained in more details about the idea of the instances of those components.