How to integrate Error Boundary in Components routed using react-router

You are not using the ErrorBoundary at the correct place. Wrapping an ErrorBoundary around input tag only make sure that if there is an error in the input, that is getting caught by the ErrorBoundary

return (
    <ErrorBoundary>   {/* errorBounday here only catches error in input */}
        <input type='button' onClick={this.throwError} value={title} />
    </ErrorBoundary>
)

You need to wrap your ErrorBoundary around components that throw the error like

<Route
          path="/comp1"
          render={() => (
            <ErrorBoundary>
              <MyComponent1 title="Component 1 Again" />
            </ErrorBoundary>
          )}
        />

In this way if the MyComponent1 throws an error it being caught by the error boundary which is the case for you. Your code would look like

class App extends React.Component {
  render() {
    return (
      <Router>
        <div style={{ backgroundColor: "green" }}>
          <div style={{ backgroundColor: "#f0f0ae", height: "30px" }}>
            <Link to="/">Link 1</Link> &#160;&#160;
            <Link to="/comp1">Link 2</Link> &#160;&#160;
            <Link to="/comp2">Link 3</Link> &#160;&#160;
          </div>

          <div style={{ backgroundColor: "#ffc993", height: "150px" }}>
            <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>
              )}
            />
          </div>
        </div>
      </Router>
    );
  }
}

DEMO

Do read this question for understanding how to test ErrorBoundaries in codesandbox


I think this answer by @Shubham Khatri is the correct one, I just want to complete it by providing a component that encapsulate the logic of wrapping a <Route> in an <ErrorBoundary>

const RouteWithErrorBoundary = (props) => {
    return (
        <ErrorBoundary key={props.location?.pathname}>
            <Route {...props} />
        </ErrorBoundary>
    );
};

Let us add some types with typescript :

const RouteWithErrorBoundary: React.FC<RouteProps> = (props) => {
    return (
        <ErrorBoundary key={props.location?.pathname}>
            <Route {...props} />
        </ErrorBoundary>
    );
};

You can then use it very easily :

<Switch>
    <RouteWithErrorBoundary path='/' exact render={() => <MyComponent1 title="Component 1" />} />
    <RouteWithErrorBoundary path='/comp1' render={() => <MyComponent1 title="Component 1 Again" />} />
    <RouteWithErrorBoundary path='/comp2' render={() => <MyComponent2 title="Component 2" />} />
</Switch>

Wrap your main router outlets with ErrorBoundary but provide a unique key to ErrorBoundary to force it to teardown and build a new instance when the location changes:

export const AppRouter = () => {
  const location = useLocation();

  return (
    <main>
      <ErrorBoundary key={location.pathname}>
        <Switch>
          <Route path="/" component={Dashboard} />
          <Route path="/orders" component={Orders} />
        </Switch>
      </ErrorBoundary>
    </main>
  );
};

Be warned, this simple but lazy solution will cause unneccessary renders of everything within ErrorBoundary whenever the location changes. You could get around this possibly by calculating a less-frequently-changing key based on the pathname instead of using the whole pathname itself.