The codepen linked to below, uses react-router
and an error boundary to catch errors within each route.
If you click on "Shop", the error is caught as expected, but the other links then no longer works. What's going on here? Why does react-router-dom
seemingly stop working? What is the "correct" way to do this? Is it a problem with the <ErrorBoundary>
component, or the way the route components are wrapped, or? 🤔
https://codepen.io/mpontus/pen/NyKNoL
In case something happens to the codepen link:
const { BrowserRouter, Switch, Route, NavLink } = ReactRouterDOM;
class ErrorBoundary extends React.Component {
state = { hasError: false };
componentDidCatch(error, info) {
this.setState({ hasError: true });
}
render() {
if (this.state.hasError) {
return <h1>An error has occured.</h1>;
}
return this.props.children;
}
}
const HomeScreen = () => <h3>Home</h3>;
const ProfileScreen = () => <h3>Profile Screen</h3>;
const ShopScreen = () => {
throw new Error("Error during render");
};
const App = () => (
<BrowserRouter>
<div className="container">
<nav className="nav nav-pills">
<NavLink exact className="nav-link" activeClassName="active" to="/">
Home
</NavLink>
<NavLink className="nav-link" activeClassName="active" to="/profile">
Profile
</NavLink>
<NavLink className="nav-link" activeClassName="active" to="/shop">
Shop
</NavLink>
</nav>
<Switch>
<Route
exact
path="/"
render={() => (
<ErrorBoundary>
<HomeScreen />
</ErrorBoundary>
)}
/>
<Route
path="/profile"
render={() => (
<ErrorBoundary>
<ProfileScreen />
</ErrorBoundary>
)}
/>
<Route
path="/shop"
render={() => (
<ErrorBoundary>
<ShopScreen />
</ErrorBoundary>
)}
/>
</Switch>
</div>
</BrowserRouter>
);
ReactDOM.render(<App />, document.getElementById("root"));
In short, since you're reusing the ErrorBoundary
for each route, it's never unmounted (this is by design). As such, its hasError
state persists across each route.
You can mitigate this by updating the state when the location changes within the ErrorBoundary
component:
componentDidUpdate(prevProps) {
if (prevProps.location.pathname !== this.props.location.pathname) {
this.setState({ hasError: false });
}
}
Since you're using the render
prop, you'll have to manually pass the route props to the ErrorBoundary
component:
For example:
<Route
exact
path="/"
render={props => (
<ErrorBoundary {...props}>
<HomeScreen {...props} />
</ErrorBoundary>
)}
/>
Working demo (since this codesandbox is in development
, it'll show an error overlay, so you'll have to close the error window to continue):