The problem is, you can’t just wrap your entire application in a try
/catch
because of the way JavaScript works.
If we tried to wrap our JSX in a try
/catch
, it would only
catch errors during the creation of those elements, but the real errors will
happen with React gets around to calling our component functions.
So you could wrap all of the contents of your components in a try
/catch
, but
that’s super annoying boilerplate:
// no, don't do this...function Calculator({ left, operator, right }) {try {const result = operations[operator](left, right)return (<div><code>{left} {operator} {right} = <output>{result}</output></code></div>)} catch (error) {return <div>Oh no! An error occurred!</div>}}// ugh, that would be terrible
They’re called Error Boundaries.
So you can make a special kind of component that implements the Error Boundary API, and then use it like so:
<ErrorBoundary fallback={<div>Oh no!</div>}><App /></ErrorBoundary>
And just like with try/catch
you can place these error boundaries wherever you
want to catch errors. You can even nest them to catch errors in different parts
of your application to give your users a more meaningful experience.
Unfortunately, React doesn’t ship this component as a built-in component, and there is currently no way to create an Error Boundary component as a modern function component so you have to use a class component instead.
Here’s a simple version of an Error Boundary component:
class ErrorBoundary extends React.Component {state = { error: null }static getDerivedStateFromError(error) {return { error }}render() {return this.state.error ? this.props.fallback : this.props.children}}
Then you’d use it like so:
<ErrorBoundary fallback={<div>Oh no!</div>}><App /></ErrorBoundary>
And now any errors thrown in App
will be caught by the Error
Boundary and render out the error message instead of crashing the app.
You’re never going to need to do this though. Instead we’ll follow the
official docs’ recommendation and use
react-error-boundary
which has a really nice API with a variety of ways to use it for different use
cases. Check 📜 the docs
for more details.
In React, you need to consider two types of errors: errors that happen during
rendering, and errors that don’t (like an event handler, a useEffect
callback,
or a promise .then
/.catch
).
Under the hood, React is using try
/catch
to catch errors that happen during
rendering, but React cannot catch all of these kinds of errors because they
happen outside of React’s call stack.
So you need to surface those to React yourself. It’s not very often you’ll need
to handle these kinds of errors in real world applications using modern React
patterns and frameworks, but you may run into cases where it’s useful. The
easiest way to do this is to use the useErrorBoundary
hook from
react-error-boundary
:
function App() {const { showBoundary } = useErrorBoundary()useEffect(() => {function handleMouseMove(event) {try {// Do something that could throw} catch (error) {showBoundary(error)}}window.addEventListener('mousemove', handleMouseMove)return () => {window.removeEventListener('mousemove', handleMouseMove)}})// Render ...}
An alternative to using an error boundary for this type of error is to store the error in state and display that. Both approaches are valid, and which one you choose comes down to which feels better. I typically try both and choose the one I hate the least 😅
Most applications will use more than a single error boundary. You should really think about error boundaries as try/catch blocks. You don’t want to wrap your entire application in a single try/catch block because it’s more difficult to handle errors in a meaningful way. Instead, you wrap things in more practical places so you can handle errors in a more localized way with the kind of context that makes sense for that part of the application.
You’ll use error boundaries in a similar way. Just think about what the user will be able to do when errors occur and if you’re able to give them more useful actions by handling the error more closely to where it originated, then do that.
function App() {return (<div><ErrorBoundary fallback={<div>Something went wrong with the list.</div>}><List /></ErrorBoundary><ErrorBoundaryfallback={<div>Something went wrong with this item, try another.</div>}><Detail /></ErrorBoundary></div>)}
Quick Links
Legal Stuff
Social Media