'React-router stops working when error boundary catches an error in route
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"));
Solution 1:[1]
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):
Solution 2:[2]
for the record, I think if you don't want to have issue of error state remain after changing route & define error boundary on each route in r.r v6, you can solve it like below:
a: define variable to save prev location:
constructor(props) {
super(props);
this.state = { error: null, errorInfo: null };
this.prevPath = null;
}
static getDerivedStateFromError(error) {
return { error: true, errorInfo: error };
}
b: set current location on component did mount
componentDidMount() {
this.prevPath = window.location.pathname;
}
c: and finally did update should be like this:
componentDidUpdate() {
if (window.location.pathname !== this.prevPath)
this.setState({ error: null, errorInfo: null });
this.prevPath = window.location.pathname;
}
d: wrap outlet like this:
<ErrorBoundry>
<Outlet />
</ErrorBoundry>
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Matt Carlotta |
Solution 2 | kingofday |