reactjsreact-routerbrowser-history

Browser navigation broken by use of React Error Boundaries


When an error is thrown in our React 16 codebase, it is caught by our top-level error boundary. The ErrorBoundary component happily renders an error page when this happens.

Where the ErrorBoundary sits

   return (
     <Provider store={configureStore()}>
       <ErrorBoundary>
         <Router history={browserHistory}>{routes}</Router>
       </ErrorBoundary>
     </Provider>
   )

However, when navigating back using the browser back button (one click), the URL changes in the address but the page does not update.

I have tried shifting the error boundary down the component tree but this issue persists.

Any clues on where this issue lies?


Solution

  • The op has probably found a resolution by now, but for the benefit of anyone else having this issue I'll explain why I think its happening and what can be done to resolve it.

    This is probably occurring due to the conditional rendering in the ErrorBoundary rendering the error message even though the history has changed.

    Although not shown above, the render method in the ErrorBoundary is probably similar to this:

    render() {
      if (this.state.hasError) {
        return <h1>An error has occurred.</h1>
      }
    
      return this.props.children;
    }
    

    Where hasError is being set in the componentDidCatch lifecycle method.

    Once the state in the ErrorBoundary has been set it will always render the error message until the state changes (hasError to false in the example above). The child components (the Router component in this case) will not be rendered, even when the history changes.

    To resolve this, make use of the react-router withRouter higher order component, by wrapping the export of the ErrorBoundary to give it access to the history via the props:

    export default withRouter(ErrorBoundary);
    

    In the ErrorBoundary constructor retrieve the history from the props and setup a handler to listen for changes to the current location using history.listen. When the location changes (back button clicked etc.) if the component is in an error state, it is cleared enabling the children to be rendered again.

    const { history } = this.props;
    
    history.listen((location, action) => {
      if (this.state.hasError) {
        this.setState({
          hasError: false,
        });
      }
    });