React components are often composed of multiple/nested subcomponents to enhance code quality. Breaking a larger component into different subcomponents is a great approach to achieve a clean and reusable structure, but sometimes this can lead to some crashes, due to problematic JavaScript code, invalid API usage, inconsistent flow, etc. These small, unexpected inconsistencies can crash the whole app, which is the least expected behavior by a user from any app. To handle these errors gracefully, React 16 introduced error boundaries components.
Error boundaries provide a convenient way to log errors while displaying a fallback UI to inform the users about the possible issue. This guide will cover the implementation of error boundaries to handle errors in the React UI hierarchy with pro tips.
Error boundaries are normal React components that act as a wrapper to catch the errors from the child components. A component can simply be converted into an error boundary component by defining either static getDerivedStateFromError(error)
or componentDidCatch(error, errorInfo)
:
1import React, { Component } from 'react';
2
3export default class ErrorBoundary extends Component {
4
5 state = { hasError: false };
6
7 static getDerivedStateFromError(error) {
8 // Update state to show the fallback UI during the next render phase
9 return { hasError: true };
10 }
11
12 componentDidCatch(error, info) {
13 // logging the error details
14 console.log(`Cause: ${error}.\nStackTrace: ${info.componentStack}`);
15 }
16
17 render() {
18 if (this.state.hasError) {
19 // Return the fallback UI
20 return <h3 style={{ 'text-align': 'center' }}>Unfortunately, something went wrong.</h3>;
21 }
22
23 return this.props.children;
24 }
25}
The difference between getDerivedStateFromError
and componentDidCatch
methods are based on:
The getDerivedStateFromError
is called during the render phase to update the state so that the fallback UI can be rendered as per the updated hasError
value from the state
object. On the other side, componentDidCatch
is called during the commit
phase that occurs after the render phase, so the ideal place to update the state
is getDerivedStateFromError
method.
As you may have observed, componentDidCatch
provides an additional info
object that can be used to fetch the error stack-trace for logging purposes.
Since the getDerivedStateFromError
is called during the render method, and the rendering phase is often slower than the commit phase, it's recommended to use componentDidCatch
for error details processing and logging tasks.
Once you have created an error boundary component, the next step is to simply wrap the component hierarchy inside the error boundary component:
1// index.js
2ReactDOM.render(
3 <ErrorBoundary>
4 <App />
5 </ErrorBoundary>,
6 document.getElementById('root')
7);
The ErrorBoundary
component wraps the App
component and will catch any possible errors from the App
or its subcomponents. The App
component has been malformed intentionally to produce an error by invoking toLowerCase
function on an undefined
value:
1export default class App extends Component {
2 state = {
3 items: ["Banana", "Mango"],
4 };
5
6 render() {
7 // error: because of invalid index 3, items[3] will return an undefined value
8 // invoking toLowerCase on undefined value will produce an error
9 const wrongItem = this.state.items[3].toLowerCase();
10
11 return (
12 <div className="container">
13 <p>{'Hello World!'}</p>
14 </div>
15 );
16 }
17}
The App
component will produce an error during the execution of the render function. The error will be caught by the ErrorBoundary
component, and it will display the fallback error UI.
Note: React will display the stack-trace automatically in development mode, but this information won't be displayed in the production app. To view the fallback error UI, remove the error detail screen in development mode by simply pressing escape or clicking on the close (
X
) icon.
The sole purpose of error boundaries is to handle the error during the execution of any lifecycle or render function of any nested subcomponent. Error boundaries do not handle errors from event-handlers, asynchronous code (Promise, fetch, etc.), and SSR (server-side rendering). Instead, a try-catch
block should be used to handle errors in event-handlers or in any asynchronous task (e.g. Promises, setInterval, requestAnimationFrame, etc.). The major differences between error boundaries and try-catch
are:
Use error boundaries to handle error occurred during the creation/updation of any component in the hierarchy, because try-catch
does not handle errors of subcomponents:
1// inside the render function of a component
2try{
3 // no error will be captured by the catch block during creation/updation of ChildComponent
4 return <ChildComponent />
5}catch(e){
6 console.log('error', e);
7}
Try-catch should be used to handle any potential issues that can occur due to invalid input/data, connection issues or bad server response, etc.
1try{
2 var response = getBooksFromStoreAPI();
3}catch(e){
4 console.log('error', e);
5}
The getBooksFromStoreAPI()
may throw an error because of invalid response or bad connection, which will be handled by the try-catch
block.
boolean
flag in state
to display the fallback error UI. Additionally, state
can store the error message details that can be used to inform users about the issue.Error boundary is a great feature to handle errors gracefully in the component hierarchy. This guide has explained the necessary details to implement error boundaries in React with best practices. Make sure to use error boundaries for a smooth user experience. Happy coding!