Quick Links

React error boundaries let you catch JavaScript errors that occur in child components. Any unhandled error originating below the boundary's tree position will be caught, preventing a crash occurring.

You can display your own fallback UI after an error boundary traps an error. This lets you gracefully communicate the problem to the user. They'll be able to keep using the rest of your interface without suffering a completely crashed tab.

Creating Error Boundaries

Any React class component can become an error boundary. You just need to set either of the following lifecycle methods:

  •         componentDidCatch(error)
        
    - This instance method will be called whenever the component catches an error. You can use this to report the error to an analytics or monitoring service.
  •         static getDerivedStateFromError(error)
        
    - This static method can be used to update your component's state after an error occurs. This is how you display a fallback UI.

Here's what the two methods look like in use:

        componentDidCatch()
    

class MyComponent extends React.Component {
    

componentDidCatch(error) {

// use custom reporting framework

logErrorToAnalytics(error);

this.props.onError(error);

}

}

Using

        componentDidCatch()
    

, your component can report the error in whichever way it sees fit. As it's an instance method, you may also pass it up the component tree via props.

        getDerivedStateFromError(error)
    

class MyComponent extends React.Component {
    

state = {error: null};

render() {

return <h1>{!this.state.error ? "Hello" : "Error"}</h1>;

}

static getDerivedStateFromError(error) {

return {error};

}

}

        getDerivedStateFromError()
    

also receives the JavaScript error object. It must return an object describing the state transformation to apply to your component.

React will pass the returned object to

        setState()
    

. In this example, the value of the

        error
    

key in the component's state will get set to the caught error object. This will result in the rendered output changing to

        Error
    

instead of the standard

        Hello
    

text.

Which Method to Use?

If the two boundary methods seem similar, it's because they are! Technically, you can define either or both of these methods and still have the same results -

        componentDidCatch()
    

could call

        setState()
    

to update your component's state, and

        getDerivedStateFromError()
    

could call an external monitoring service to report errors it captures.

The difference lies in the phase in which the error is caught.

        componentDidCatch()
    

captures errors in the commit phase, after React has updated the DOM.

        getDerivedStateFromError()
    

will be called during the render phase, before React updates the browser's DOM.

This timing subtlety explains why

        getDerivedStateFromError()
    

is generally used to switch to the fallback UI. When a serious error occurs, the act of updating the DOM might provoke further errors if your app's been left in an inconsistent state. Updating the state prior to the DOM update occurring ensures the fallback UI renders immediately.

Locating Your Error Boundaries

You're free to use error boundaries wherever you see fit. It's good practice to use multiple error boundaries. Add an error boundary for each major layer of your UI. This lets you isolate errors in your page content from the application shell, so a crash in a route doesn't take out your navigation bar.

Here's a simple component hierarchy:

export const () => (
    

<App>

<Header />

<Router />

<Footer />

</App>

);

In this application, the

        App
    

component is a simple wrapper managing top-level state.

        Header
    

renders a navigation bar and

        Footer
    

displays the bottom bar. The main page content - where crashes are most likely to occur - is loaded dynamically by

        Router
    

, based on the current URL.

By default, a crash within the

        Router
    

children knocks out the entire site. By placing an error boundary around

        Router
    

, errors occurring within the component can be gracefully handled. The header and footer remain usable while the main page content is replaced with a fallback message.

The app needs at least one more error boundary. Wrapping the children of

        App
    

ensures errors arising within the header or footer can be caught. In this situation, it might be acceptable to replace the entire UI with a full-page error message.

Here's the refactored component structure:

class ErrorBoundary extends React.Component {
    

state = {error: null};

render() {

if (!this.state.error) return this.props.children;

else return <h1>Error!</h1>;

}

static getDerivedStateFromError(error) {

return {error};

}

}

export const () => (

<App>

<ErrorBoundary>

<Header>

<ErrorBoundary>

<Router />

</ErrorBoundary>

<Footer />

</ErrorBoundary>

</App>

);

We've abstracted the error boundary logic into a reusable component. We can now wrap

        ErrorBoundary
    

around any components which should be isolated from their parents. Remember you don't have to create an error boundary component - for simple applications, or a specific UI component, you can add the lifecycle hooks directly into a component class.

Limitations of Error Boundaries

Error boundaries have some important limitations you should be aware of. They're capable of catching most unhandled JavaScript errors but some will go undetected.

Error boundaries won't intercept errors that occur in event handler methods. Event handler code doesn't affect React's rendering process so the framework can still render your components. As event handler errors won't result in corrupted UI or component unmounts, React doesn't try to intercept them.

If you need to respond to errors in your event handlers, you must use a regular

        try
    

/

        catch
    

block. Perform a state update in the

        catch
    

statement to switch your UI into an error state.

class MyComponent extends React.Component {
    

state = {error: null};

handleClick = () => {

try {

doSomething();

}

catch (error) {

this.setState({error});

}

}

render() {

if (this.state.error) return <p>Error!</p>;

else return <button onClick={this.handleClick}>Submit</button>

}

}

Aside from event handlers, error boundaries can't detect errors that occur in asynchronous code. If you're using Promises,

        async
    

/

        await
    

, or

        setTimeout()
    

, you should make sure you've using

        try
    

/

        catch
    

/

        Promise.catch()
    

blocks to catch any errors that might occur.

A common misunderstanding around error boundaries concerns the tree they monitor. They can only catch errors that occur deeper in the tree. Error boundaries won't catch errors thrown by the boundary component itself.

export default () => (
    

<App> // Errors won't be caught

<ErrorBoundary> // Errors won't be caught

<Router /> // Errors thrown here will be caught

</ErrorBoundary>

</App>

);

Each error boundary must wrap around the components that might throw an error.

Finally, only class-based components can be error boundaries. There's currently no mechanism to let a functional component become an error boundary. If you're working in a functional codebase, you should create a reusable error boundary component like that shown above. You can then wrap your components with it each time you need an error boundary.

Conclusion

Error boundaries bring JavaScript's

        try
    

/

        catch
    

to React's declarative rendering model. They let you isolate parts of your site's UI, so a crash in one component won't affect its siblings.

You should evaluate your app's UI to identify the critical sections. Place error boundaries strategically to stop an unhandled error from unmounting your entire component tree. Users are much more likely to accept a fully stylised "something's gone wrong" than a white screen that needs refreshing.