Understanding Error Boundaries in React

Using the analog for try/catch in React components.

In JS code, we use the try-catch block to catch errors and handle them gracefully. Without them, our code will come crashing with some nasty error messages.

In React, we often encounter UI errors where React will dismount the whole component tree to show error messages in the DOM.

UI errors occur when we try to render the wrong type of data (for example, an object).

class App extends React.Component {

constructor() {

this.state = {

details: {

name: "nnamdi",

age: 27

}

}

} render() {

return (

<div>

{this.state.details}

</div>

)

}

}

Even though React is written in JS, we cannot use try/catch to catch and handle UI rendering errors. Instead, React gives us the Error boundaries where UI/rendering errors can be caught and handled without the whole component tree dismounting.

A Component becomes an error boundary if it implements the componentDidCatch lifecycle hook or the getDerivedStateFromError static method.

Handling Errors in Reusable UI Components

It has become increasingly popular to install/clone reusable components from cloud component hubs like Bit.dev.

In most cases, these components have been published to serve as your organization’s design system or simply as a way for your team to share reusable components.

It may happen that you use components from unknown sources in the open-source community. If that’s the case, you may want to wrap your components with an error boundary, to be on the safe side and prevent a total crash.

Exploring React components published on Bit.dev

componentDidCatch

This lifecycle method is invoked when a descendant component throws an error.

componentDidCatch(err, info)

It receives two parameters, err and info. err is the error thrown in the component’s children, and info is the information on the error thrown.

For example, we have an App component:

class App extends React.Component {

constructor() {

this.state = {

details: {

name: "nnamdi",

age: 27

}

}

} render() {

return (

<div>

{this.state.details}

</div>

)

}

}

This component will throw an error because it tries to render an object this.state.details . To catch this error we will create a component ErrorBoundary that implements the componentDidCatch method and enclose the App component inside the ErrorBoundary tags :

class ErrorBoundary extends React.Component {

constructor(props) {

super(props)

this.state = {

// checks if an error has occured in its children.

hasError: false

}

} componentDidCatch(err, info) {

// set the the hasError state to true so on the next render it will display the `<div>Error occured.</div>` in the DOM.

this.setState({hasError: true})

} render() {

if(this.state.hasError) {

// if the hasError state boolean is true, it returns this to tell the user an error has occurred

return (

<div>Error occurred.</div>

)

} else {

// if there is no error the children components are returned so there are rendered.

return this.props.children

}

} }

This ErrorBoundary component implements the componentDidCatch method. The componentDidCatch is called whenever the descendant components throw an error. It will be called with the error err , and the error information info.

So, in the ErrorBoundary component, it has a local state that checks for error. The componentDidCatch sets the hasError state to true via setState because it is called when an error is thrown in its children.

In the render method, ErrorBoundary checks if there is an error by checking if the hasError has a true value, so it returns <div>Error occurred.</div> to tell the user an error was thrown in its children components. This is the fallback UI.

If the hasError in the state is not true it returns the children components in the props.children .

We are done, the next step is to wrap the App into ErrorBoundary:

<ErrorBoundary>

<App />

</ErrorBoundary>

Now, any error thrown in the App component will be caught and handled by the ErrorBoundary.

Instead of the long red error messages, the ErrorBoundary caught the error and peacefully displayed the Error occurred message without dismounting the entire component tree.

getDerivedStateFromError

This is a static method. It is called when a child (or children) component throws an error.

static getDerivedStateFromError(error)

It is called with the error thrown. So we can handle it in our component. It returns a value that updates the state.

Let’s see an example:

class ErrorBoundary extends React.Component {

constructor(props) {

super(props)

this.state = {

hasError: false

}

} static getDerivedStateFromError(error) {

return { hasError: true }

} render() {

if(this.state.hasError) {

return (

<div>Error occurred.</div>

)

} else {

return this.props.children

}

} }

Our same ErrorBoundary component, just that we refactored it to use the static getDerivedStateFromError instead of componentDidCatch method. It will work the same as before.

🚩 Important Notes

Error boundary components are class components, they cannot be used in function components.

You can use Error boundaries anywhere in the component tree. It depends on the component level you want to catch and handle the errors they throw.

Error boundaries do not catch errors for:

Event handlers

Asynchronous code (e.g. setTimeout or requestAnimationFrame callbacks)

Server-side rendering

Errors are thrown in the error boundary itself (rather than its children)

Error boundaries work like JS catch {} block.

If an error boundary fails to catch and handle an error, it is propagated to the closest error boundary.

Conclusion

Error boundary is great. It gives us the power to handle errors in our components just like we do use the try{} catch{} block.

If you have any questions regarding this or anything I should add, correct or remove, feel free to comment, email or DM me.

Thanks !!!

Credits

Learn More