Generic error handler in ReactJs

A vital part of any application is error handling. In one of our recent ReactJs projects we approached this by creating a component that wouldn’t render anything to the screen but could keep track of changes to state and respond accordingly. Here’s how…

The component

It’s a pretty simple one, that just extends React.Component. However, its render method returns null, since we don’t want it to render anything to the screen, only respond to changes in state.

In order to respond in this way, we used the ‘componentWillReceiveProps’ method which fires whenever the components state changes. Here’s a simplified version of the handler to illustrate…

export class ErrorHandler extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
        hasError: false
    };
  }

  componentWillReceiveProps(nextProps) {
    if (nextProps.hasError) {
        browserHistory.push('error');
    }
  }

  render() {
    return null;
  }
}

ErrorHandler.propTypes = {
  hasError: PropTypes.bool
};

const mapStateToProps = state => ({
  hasError: state.hasError
});

export default connect(mapStateToProps)(ErrorHandler);

As can be seen, a single boolean ‘hasError’ state variable is mapped to props. Whenever this prop changes, the ‘componentWillReceiveProps’ method executes. If there was an error, in this instance, the browser is redirected to the ‘error’ route.

This of course, relies on a relevant action being fired off and then captured by a related reducer.

Authentication

Another scenario may be a react app that needs authentication. In our app, all orchestration endpoints required an authenticated user in order to access them. If an endpoint was hit without being authenticated, orchestration would send a 401 unauthorised response with an authentication url. This would then redirect the user to first log in, and then it would redirect them back to the app. Here’s the changes to the error handler component…

constructor(props) {
    super(props);
    this.state = {
        hasError: false,
        response: {
            status: 200,
            authenticateUrl: null
        }
    };
}

componentWillReceiveProps(nextProps) {

    if (nextProps.error.hasError) {
        if (nextProps.error.response.status === 401 && nextProps.error.response.authenticateUrl !== null) {
            window.location.assign(nextProps.error.response.authenticateUrl);
        } else {
            browserHistory.push('error');
        }
    }
}

Here, the state variable is more than just a simple boolean. It also includes a response with a status and optional authenticateUrl. In the ‘componentWillReceiveProps’ method, it first checks whether some error happened. However, if the status is 401 and the authenticateUrl isn’t null, it’ll redirect the browser to that external url.

Testing

In order to test this component, we can stub out the ‘push’ method of browserHistory and spy on the ‘assign’ method of window.location. We then create the component and pass it some props, update the props to be in the error state, then do some assertions.

it('should redirect to the error route', () => {
    router.browserHistory = { push: () => {} };  // 1
    const stub = sinon.stub(router.browserHistory, 'push');
    const props = {
        hasError: false,
        response: { }
    };
    const c = shallow(<ErrorHandler {...props} />);  // 2
    c.setProps({  // 3
        error: {
            hasError: true,
            response: {
                status: 404,
                authenticateUrl: null
            }
        }
    });
    expect(stub.called).to.equal(true);

    stub.restore();
});
  1. Create a dummy ‘push’ method and stub it out so it doesn’t do anything
  2. Create the component and pass it props in a non-error state
  3. Set the props to be in an error state
  4. Expect the stub to have been called

To test the authentication error route we do exactly the same thing except we create a dummy ‘assign’ method and spy on it (we’re using sinon here)

window.location.assign = () => {};
const assignSpy = sinon.spy(window.location, 'assign');

Since this runs in a node environment, the window.location object doesn’t contain an ‘assign’ method so we need to create it and then spy on it.

Conclusion

That’s about it. This component simply observes state and performs some redirection. It can then be added to your app component somewhere. Happy error handling 🙂

One thought on “Generic error handler in ReactJs

Leave a Reply