Browser navigation broken by use of React Error Boundaries
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,
});
}
});
To add to jdavies' answer above, make sure you register the history listener in a componentDidMount
or useEffect
(using []
to denote it has no dependencies), and unregister it in a componentWillUnmount
or useEffect
return statement, otherwise you may run into issues with setState
getting called in an unmounted component.
Example:
componentDidMount() {
this.unlisten = this.props.history.listen((location, action) => {
if (this.state.hasError) {
this.setState({ hasError: false });
}
});
}
componentWillUnmount() {
this.unlisten();
}