Error Boundaries basically provide some sort of boundaries or checks on errors, They are React components that handle JavaScript errors in their child component tree.
React components that catch JavaScript errors anywhere in their child component tree, log them, and display a fallback UI. It detects errors during rendering, in lifecycle methods, etc.
Why do we Use Error Boundaries? What are the reasons behind this?
Suppose there is an error in JavaScript inside a component then it is used to corrupt React’s internal state and cause it to emit cryptic errors.
Error boundaries help in removing these errors and display a Fallback UI instead (Which means a display of an error that something broke in the code).
The illustration below shows the component tree of a simple React app. It has a header, a sidebar on the left, and the main component, all of which is wrapped by a root <App /> component.
On rendering these components, we arrive at something that looks like the picture below.
In an ideal world, we would expect to see the app rendered this way every single time. But, unfortunately, we live in a non-ideal world. Problems, (bugs), can surface in the frontend, backend, developer’s end, and a thousand other ends. The problem could happen in either of our three components above. When this happens, our beautifully crafted app comes crashing down like a house of cards.
React encourages thinking in terms of components. Composing multiple smaller components is better than having a single giant component. Working this way helps us think about our app in simple units. But aside from that won’t it be nice if we could contain any errors that might happen in any of the components? Why should a failure in a single component bring down the whole house?
React 16 came to the rescue with the concept of an “error boundary”. The idea is simple. Erect a fence around a component to keep any fire in that component from getting out.
The illustration below shows a component tree with an <ErrorBoundary /> component wrapping the <Main /> component. Note that we could certainly wrap the other components in an error boundary if we wanted. We could even wrap the <App /> component in an error boundary.
The red outline in the below illustration represents the error boundary when the app is rendered.
As we discussed earlier, this red line keeps any errors that occur in the <Main /> component from spilling out and crashing both the <Header /> and <LeftSideBar /> components. This is why we need an error boundary.
Now that we have a conceptual understanding of an error boundary, let’s now get into the technical aspects.
What’s the Working Principle of Error Boundaries?
Error Boundary works almost similar to catch in JavaScript. Suppose an error is encountered then what happens is as soon as there is a broken JavaScript part in Rendering or Lifecycle Methods, It tries to find the nearest Error Boundaries Tag.
Creating React Application:
Step 1: Create a React application using the following command:
Step 2: After creating the error-boundary directory move to it.
Project Structure:
It will look like the following.
Example: Now write down the following code in the App.js file. Here, App is our default component where we have written our code.
Filename: App.js
import React from “react”;
class ErrorBoundary extends React.Component {
// Constructor for initializing Variables etc in a state
// Just similar to initial line of useState if you are familiar
// with Functional Components
constructor(props) {
super(props);
this.state = { error: null, errorInfo: null };
}
// This method is called if any error is encountered
componentDidCatch(error, errorInfo) {
// Catch errors in any components below and
// re-render with error message
this.setState({
error: error,
errorInfo: errorInfo
})
// You can also log error messages to an error
// reporting service here
}
// This will render this component wherever called
render() {
if (this.state.errorInfo) {
// Error path
return (
<div>
<h2>An Error Has Occurred</h2>
<details>
{this.state.error && this.state.error.toString()}
<br />
{this.state.errorInfo.componentStack}
</details>
</div>
);
}
// Normally, just render children, i.e. in
// case no error is Found
return this.props.children;
}
}
// This is a component for Counter,Named Counter
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = { counter: 0 };
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(({ counter }) => ({
counter: counter + 1
}));
}
render() {
if (this.state.counter === 3) {
// Simulate a JS error
throw new Error(‘Crashed!!!!’);
}
return <h1 onClick={this.handleClick}>{this.state.counter}</h1>;
}
}
function App() {
return (
<div style={{ marginLeft: ’30px’, marginTop: ’50px’ }}>
<div style={{ textAlign: “center” }}>
<h1>
<strong>To see the working of Error boundaries
click on the Counters to increase the value
</strong>
</h1>
<p>
Program is made such a way that as soon as the counter
reaches the value of 3, Error boundaries will throw an
error.
</p>
</div>
<hr style={{ width: “500px” }} />
<ErrorBoundary>
<p>
These two counters are inside the same error boundary.
If one crashes, then the effect will be done on both
as the error boundary will replace both of them.
</p>
<Counter />
<Counter />
</ErrorBoundary>
<hr style={{ width: “500px” }} />
<p>
These two counters are each inside of their
own error boundary. So if one crashes, the
other is not affected.
</p>
<ErrorBoundary><Counter /></ErrorBoundary>
<ErrorBoundary><Counter /></ErrorBoundary>
</div>
);
}
export default App;
Filename: index.js
import React from ‘react’;
import ReactDOM from ‘react-dom’;
import ‘./index.css’;
import App from ‘./App’;
import reportWebVitals from ‘./reportWebVitals’;
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById(‘root’)
);
reportWebVitals();
Explanation:
The above code is written in such a way that if the counter reaches the value of 3 then Error Boundaries will throw an error.
As shown in the above code that two counters are included in the same Error Boundary Component through which if any one of them causes any sort of error by reaching the value of 3, then instead of rendering any of them a detailed message will be provided on the screen.
On the other end below both counters are included in the individual error Boundaries component through which what happens is only that counter which has caused the error is not rendered, while others are rendered normally.
Error boundaries do not catch errors for the following events:
- Event Handlers
- Asynchronous code(For example request Animation Frame etc)
- Server-Side Rendering
- Errors are thrown in the error boundary itself (rather than its children)
Try/Catch:
One question which might be tickling in your mind is since Error Boundaries works like Catch, Why not just go with try/catch and why should you learn this new Concept? Well, the answer is try/catch is used with imperative code but As we know React is declarative in nature, and Error Boundaries help in preserving the declarative nature of React.
Uncaught changes:
Since it does not catch errors in some particular cases, So what about those errors that are left unchecked or Uncaught? As of React 16, errors that were not caught by any error boundary will result in unmounting of the whole React component tree. This means after migrating to React 16 and using Error Boundaries, you will be able to provide a better user experience as now users will be able to see the reason before an unexpected crash, instead of just guessing.
Component Stack Trace:
React 16 prints all errors that occurred, it provides a component Stack Trace. This helps the user in identifying the point where an error has occurred.
Error Boundaries:
- It can only be used with Class Components.
- It does not catch errors for Event Handlers, Asynchronous code(For example request Animation Frame), or Server Side Rendering, and Errors are thrown in the error boundary itself (rather than its children).
- It is available only in react 16 or after.