'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):

Edit Error Boundary

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