Catching Asynchronous Errors in React using Error Boundaries
React 16.0 introduced Error Boundaries.
Error boundaries are React components that catch JavaScript errors anywhere in their child component tree, log those errors, and display a fallback UI instead of the component tree that crashed.
Take this example:
If we run this code, the ErrorBoundary
will catch the error, render the fallback UI (Something went wrong
) and log the error to the console.
Error Boundaries are a nice way to centralize error handling in React apps but they can only catch certain errors:
Error Boundaries do not catch errors for:
* Event handlers
* Asynchronous code (e.g.
setTimeout
orrequestAnimationFrame
callbacks)* Server side rendering
* Errors thrown in the error boundary itself (rather than its children)
Most applications fetch data asynchronously, so if our BrokenComponent
does that, our ErrorBoundary
won’t catch the error and nothing would be rendered. We’ll notice the error because the browser will issue an error in the console (an Uncaught (in promise)
on Chrome).
The usual way to fix this is having our component handling its own errors.
But this is such a let down. We cannot use our Error Boundaries to manage all errors in the same fashion, which can lead to duplication.
The problem is that asynchronous code runs outside the render and commit phases of React. How can we fix it then? The solution is right in front of us. Luckily, Dan Abramov is always there to open our eyes:
Just use setState
. Throw inside the callback. See how errors are caught by your Error Boundaries.
Let’s extract this snippet to a reusable hook:
And use it in our code:
And that’s it. Now our ErrorBoundary
can deal with all errors, both synchronous and asynchronous.
Parting thoughts and a concurrent future
The proposed solution is simple, but to be honest, feels a bit hackish. We are forcing the asynchronous error into the React render phase using a hammer (poor setState
). We are not totally comfortable with this, but we haven’t found another option yet. So, if someone has a better solution we are all ears 😃.
Anyway, I sleep more soundly these days after digging into the new React concurrent mode. We will be writing code like the following, which fits this pattern of handling all errors with Error Boundaries:
All this stuff it’s still experimental but it seems that the final version will be very similar (if not the same). It’s good to have our code future proof.