React is a library for creating front end views. It has a big ecosystem of libraries that work with it. Also, we can use it to enhance existing apps.
We can handle the error gracefully with error boundary components.
In this article, we’ll look at how to define and use them.
Error Boundaries
JavaScript inside components used to corrupt React’s internal state and cause cryptic errors to be emitted on the next renders.
These errors are always caused by earlier errors in the application code. React didn’t provide a way to handle them gracefully in components and recover from them.
We can use error boundaries to catch JavaScript errors anywhere in the child component tree, log those errors and display a fallback UI instead of crashing the app.
However, error boundaries don’t catch errors for event handlers, asynchronous code, server-side rendering, or errors thrown in the error boundary itself.
We can create an error boundary component by adding the static getDerivedStateFromError()
or componentDidCatch()
methods inside a class-based component.
For example, we can use it as follows:
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
console.log(error, errorInfo);
}
render() {
if (this.state.hasError) {
return <h1>Error occured.</h1>;
} return this.props.children;
}
}
class Button extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
increment() {
this.setState({ count: this.state.count + 1 });
}
render() {
if (this.state.count === 5) {
throw new Error("error");
}
return (
<>
<button onClick={this.increment.bind(this)}>Click Me</button>
<br />
<p>{this.state.count}</p>
</>
);
}
}
class App extends React.Component {
render() {
return (
<div>
<ErrorBoundary>
<Button />
</ErrorBoundary>
</div>
);
}
}
In the code above, we have the ErrorBoundary
component, which has the componentDidCatch
method to log the errors given by the parameters.
Then we added the static getDerivedStateFromError
to set the hasError
state to true
if an error occurred.
In the render
method, we render the error message if hasError
state is true
. Otherwise, we display the child components as usual.
In the Button
component, we have a button
element that increases the count
state it’s clicked. We have an if
statement for this.state.count
which throws an error if it reaches 5.
Once it reaches 5, the ‘Error occurred’ message from the ErrorBoundary
component will be shown since the error is thrown in the render
method, which isn’t an event handler, async code, and other places error boundaries can’t catch.
As we can see, we placed the ErrorBoundary
outside any code that catches errors. This way, ErrorBoundary
can actually catch them.
During development, the whole stack trace will be displayed, we have to disable that in production. This will be done automatically when we make a production build with Create React App.
Catching Errors Inside Event Handlers
We have to use try/catch
block to catch errors in event handlers.
For example, we can write the following code to catch those:
class App extends React.Component {
constructor(props) {
super(props);
this.state = { error: null, count: 0 };
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
try {
if (this.state.count === 5) {
throw new Error("error");
}
this.setState({ count: this.state.count + 1 });
} catch (error) {
this.setState({ error });
}
}
render() {
if (this.state.error) {
return <h1>Error occurred.</h1>;
}
return (
<button onClick={this.handleClick}>Count: {this.state.count}</button>
);
}
}
In the handleClick
method, we have an if
statement that throws an error if this.state.count
is 5.
If an error is thrown, the catch
block will set the error
state to the error
object.
This will then render the ‘Error occurred’ message instead of the button to increase the count
state.
Conclusion
We can use error boundary components to handle errors gracefully and log them.
To create an error boundary component, we add a static getDerivedStateFromError()
or componentDidCatch()
method to do that.
We use componentDidCatch
to log errors and getDerivedStateFromError
to render a fallback UI.
We wrap the error component around child components that may throw errors.
Error boundary can catch errors in places other than async code, event handlers, server-side rendering code, or errors thrown in the error boundary itself.