Error Handling in React Applications: React Error Boundary

Agam Kakkar
12 min readMay 3, 2024

--

When constructing applications, encountering errors is virtually unavoidable; they might arise from server glitches, edge cases, or a myriad of other causes. Consequently, numerous techniques have been devised to mitigate these errors’ impact on both user and developer experiences.

Within React, error boundaries represent one such approach. In this piece, we’ll delve into React error management by exploring the utilization of react-error-boundary.

Error boundaries play a vital role in managing errors within React applications. These specialized React components intercept JavaScript errors occurring anywhere in their child component hierarchy, logging them and substituting a fallback UI in place of the crashed component tree. Think of them as akin to a JavaScript catch {} block but tailored for React components.

Before the advent of error boundaries, unhandled errors within components could cascade, resulting in blank screens or dysfunctional user interfaces, adversely affecting the overall user experience. However, with the introduction of error boundaries, these errors can be isolated and handled effectively.

Error boundaries can be strategically placed around the entire application or specific components to provide precise control. It’s essential to understand that they intercept errors during rendering, in lifecycle methods, and constructors throughout the subtree beneath them. However, they don’t catch errors originating from:

  • Event handlers (for which regular try/catch blocks should be used)
  • Asynchronous code (e.g., setTimeout or requestAnimationFrame callbacks)
  • Server-side rendering
  • Errors thrown directly within the error boundary itself (as opposed to its children)

Introduced in React version 16, error boundaries require the implementation of one or both of the following lifecycle methods within a class component:

  1. getDerivedStateFromError(): This method renders a fallback UI following an error. It operates during the render phase, disallowing side effects.
  2. componentDidCatch(): Utilized for logging error details, this method operates during the commit phase, allowing side effects.

Let’s illustrate with a straightforward example of a class component incorporating both getDerivedStateFromError() and componentDidCatch() lifecycle methods, sourced from React documentation:

class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}

static getDerivedStateFromError(error) {
// Update state to display the fallback UI.
return { hasError: true };
}

componentDidCatch(error, errorInfo) {
// Log the error to an error reporting service.
console.log(error, errorInfo);
}

render() {
if (this.state.hasError) {
// Render a custom fallback UI.
return <h1>Something went wrong.</h1>;
}

return this.props.children;
}
}

// Usage in a component
class App extends React.Component {
render() {
return (
<ErrorBoundary>
<MyComponent />
</ErrorBoundary>
);
}
}

In this example, the ErrorBoundary React component functions as an error handler within its child component tree. It captures JavaScript errors, records them, and proceeds to display a fallback UI. Should an error arise within MyComponent, it gets intercepted by ErrorBoundary, logged to the console, and replaces the problematic component tree with a message indicating, “Something went wrong.”

Although class components and their lifecycle methods offer a means to establish error boundaries, the react-error-boundary library elevates this functionality, simplifying the process and enhancing user-friendliness. This compact library offers a versatile solution for managing JavaScript errors within React components.

Utilizing React Hooks and functional components, react-error-boundary adopts a contemporary approach, aligning seamlessly with prevailing trends in React development. Its primary component, ErrorBoundary, presents a straightforward mechanism for encapsulating code susceptible to errors.

ErrorBoundary Component

The ErrorBoundary component from the react-error-boundary library offers a prop called fallbackRender (or fallbackUI) where developers can provide a function or a React element to display when an error occurs. Additionally, it features a resetKeys prop, enabling the resetting of component state when specific props change.

What’s remarkable about react-error-boundary is its ability to automate the creation of class components and state management, relieving developers of this burden. This allows them to concentrate on application development. Let’s explore an example of how we can utilize react-error-boundary in a component:

import { ErrorBoundary } from 'react-error-boundary'

function MyFallbackComponent({ error, resetErrorBoundary }) {
return (
<div role="alert">
<p>Something went wrong:</p>
<pre>{error.message}</pre>
<button onClick={resetErrorBoundary}>Try again</button>
</div>
)
}

function MyComponent() {
// Some component logic that may throw JS errors
}

function App() {
return (
<ErrorBoundary
FallbackComponent={MyFallbackComponent}
onReset={() => {
// Reset the state of your app here
}}
resetKeys={['someKey']}
>
<MyComponent />
</ErrorBoundary>
)
}

The ErrorBoundary component from the react-error-boundary library offers a prop called fallbackRender (or fallbackUI) where developers can provide a function or a React element to display when an error occurs. Additionally, it features a resetKeys prop, enabling the resetting of component state when specific props change.

What’s remarkable about react-error-boundary is its ability to automate the creation of class components and state management, relieving developers of this burden. This allows them to concentrate on application development. Let’s explore an example of how we can utilize react-error-boundary in a component:

import { ErrorBoundary } from 'react-error-boundary'
function MyFallbackComponent({ error, resetErrorBoundary }) {
return (
<div role="alert">
<p>Something went wrong:</p>
<pre>{error.message}</pre>
<button onClick={resetErrorBoundary}>Try again</button>
</div>
)
}
function MyComponent() {
// Some component logic that may throw JS errors
}
function App() {
return (
<ErrorBoundary
FallbackComponent={MyFallbackComponent}
onReset={() => {
// Reset the state of your app here
}}
resetKeys={['someKey']}
>
<MyComponent />
</ErrorBoundary>
)
}

In this example, whenever the ErrorBoundary detects an error, MyFallbackComponent is rendered. It showcases the error message and provides a button to reset the error state and attempt rendering the component again. The onReset prop facilitates cleaning up any preceding side effects, and the resetKeys prop controls when the component state resets.

Furthermore, ErrorBoundary includes an onError prop, a function invoked each time an error is captured. Developers can leverage this prop to log errors to an error reporting service. Here’s an example of how to implement error logging:

// Error logging function
function logErrorToService(error, info) {
// Use your preferred error logging service
console.error("Caught an error:", error, info);
}

// App component
function App() {
return (
<ErrorBoundary FallbackComponent={ErrorFallback} onError={logErrorToService}>
<MyComponent />
</ErrorBoundary>
);
}

Resetting error boundaries

The reset capability of react-error-boundary is a potent feature, enabling the clearing of error boundary states, thereby attempting to re-render the component tree. This functionality proves invaluable for handling transient errors, such as network disruptions.

To reset the error boundary, developers can utilize the resetErrorBoundary function, which is provided to the fallback component. Typically, this function is invoked in response to user interactions, such as clicking a button, facilitating manual retry of failed operations.

Moreover, the ErrorBoundary component accepts an onReset prop, a function invoked just before the error state is reset. This function serves to execute any necessary cleanup or state resets in the application before initiating a re-render after an error.

Lastly, the resetKeys prop, an array of values, triggers a reset of the error boundary when its elements change. This mechanism proves advantageous when specific prop or state changes are expected to resolve the error. Here’s an example illustrating the usage of these props:

import { ErrorBoundary } from 'react-error-boundary'

function ErrorFallback({ error, resetErrorBoundary }) {
return (
<div role="alert">
<p>Something went wrong:</p>
<pre>{error.message}</pre>
<button onClick={resetErrorBoundary}>Try again</button>
</div>
)
}

function MyComponent({ someKey }) {
// Some component logic that may throw JS errors
}

function App() {
const [someKey, setSomeKey] = React.useState(null)

return (
<ErrorBoundary
FallbackComponent={ErrorFallback}
onReset={() => setSomeKey(null)} // reset the state of your app here
resetKeys={[someKey]} // reset the error boundary when `someKey` changes
>
<MyComponent someKey={someKey} />
</ErrorBoundary>
)
}

In this example, if MyComponent encounters an error, ErrorFallback is rendered, presenting the error message and a “Try again” button. Clicking the button invokes resetErrorBoundary, triggering the onReset function, and clearing the error state, ultimately re-rendering MyComponent. Additionally, any change to the someKey prop also triggers a reset of the error boundary, offering a flexible recovery mechanism based on changes in the application’s state.

useErrorHandler Hook

The useErrorHandler Hook offered by react-error-boundary is a valuable addition to your toolkit. It’s a custom React Hook designed to enable throwing errors from within your function components. These thrown errors are then intercepted by the nearest error boundary, mimicking the behavior of throwing errors from a class component’s lifecycle methods or render function.

This hook proves especially handy when dealing with asynchronous code, where throwing an error wouldn’t typically be caught by a component’s error boundary. Here’s how you can employ useErrorHandler:

import { useErrorHandler } from 'react-error-boundary'

function MyComponent() {
const handleError = useErrorHandler()

async function fetchData() {
try {
// fetch some data
} catch (error) {
handleError(error)
}
}

return (
// JSX elements
);
}

function App() {
return (
<ErrorBoundary FallbackComponent={ErrorFallback}>
<MyComponent />
</ErrorBoundary>
);
}

In this example, MyComponent utilizes useErrorHandler to obtain a function capable of being invoked with an error. The fetchData function, an asynchronous function, fetches data and catches any potential errors. If an error arises, it’s passed to the handleError function, which then throws the error for interception by the ErrorBoundary. This mechanism ensures that errors occurring within asynchronous code are appropriately handled by the error boundary.

The useErrorHandler provides a powerful way to handle errors in function components. It works seamlessly with react-error-boundary’s ErrorBoundary component, making error handling in React a much more straightforward process.

withErrorBoundary function as HOC

The withErrorBoundary higher-order component (HOC) from the react-error-boundary package offers a solution for integrating error boundaries with class components or adhering to a higher-order component pattern. This feature proves beneficial in scenarios where Hooks and functional components might not be suitable or when you prefer a different coding style.

Here’s how you can employ withErrorBoundary:

import { withErrorBoundary } from 'react-error-boundary'

function MyComponent() {
// Your component logic
}

const MyComponentWithErrorBoundary = withErrorBoundary(MyComponent, {
FallbackComponent: ErrorFallback,
onError: logErrorToService,
onReset: handleReset,
resetKeys: ['someKey']
});

function App() {
return <MyComponentWithErrorBoundary someKey={someKey} />
}

In this example, MyComponent is wrapped with an error boundary using withErrorBoundary. The second argument to withErrorBoundary is an options object, where you can provide the same props that you’d pass to the ErrorBoundary component: FallbackComponent, onError, onReset, and resetKeys.

This HOC approach serves as an elegant solution when you want to incorporate error boundaries into your components without altering their implementation. It’s particularly useful when working with class components that cannot utilize Hooks. This demonstrates the versatility of react-error-boundary in accommodating various coding styles and paradigms in React development.

Advantages of using react-error-boundary

React-error-boundary offers numerous benefits that make it an indispensable solution for error handling in React applications. Here’s a comprehensive overview of its key advantages:

  1. Simplicity: The library provides a straightforward and intuitive API, making error handling easy to comprehend and implement. It abstracts away the complexities of error management, offering a simple yet powerful way to handle errors effectively.
  2. Function Component Friendly: Unlike traditional error boundaries in React, which are primarily designed for class components, react-error-boundary is specifically built with function components in mind. It leverages Hooks, aligning seamlessly with modern React development practices and facilitating smoother integration with function-based components.
  3. Versatility: React-error-boundary offers multiple approaches to implementing error boundaries, including as a standalone component, a higher-order component (HOC), or through custom Hooks. This versatility empowers developers to choose the most suitable method for their project requirements and coding preferences.
  4. Customizable Fallback UI: The library allows developers to create custom fallback UIs that are displayed when errors are caught. This enables the application to gracefully handle errors, providing users with informative feedback instead of crashing or displaying blank screens.
  5. Reset Functionality: React-error-boundary includes functionality to reset the error state, allowing the application to recover from errors. This feature is particularly useful for transient errors that can be resolved without requiring a complete page reload.
  6. Error Reporting: With the onError prop, errors can be logged to an error reporting service, facilitating debugging and issue resolution. This ensures that developers have access to valuable information to identify and address errors effectively.
  7. Community and Maintenance: React-error-boundary is actively maintained and widely used within the React community, ensuring regular updates and improvements. This fosters a supportive ecosystem where developers can rely on the library’s stability and ongoing support.

Catching all errors and retry mechanisms

Implementing error boundaries correctly is crucial for ensuring robust error handling in React applications. The react-error-boundary library simplifies this process by enabling the catching of errors from any part of the component tree, whether they originate from class component lifecycle methods, function component render functions, or asynchronous code when using the useErrorHandler Hook. However, merely catching errors isn’t sufficient; it’s equally important to determine the appropriate actions to take once an error is caught. This is where retry mechanisms become essential.

A retry mechanism allows the application to recover from an error by attempting the failed operation again. React-error-boundary provides native support for retry mechanisms through the resetErrorBoundary function and the resetKeys prop. The resetErrorBoundary function clears the error and triggers a re-render of the component tree, allowing users to retry a failed operation manually, typically in response to a button click.

The resetKeys prop is an array of values that, when modified, triggers a reset of the error boundary. This feature empowers the error boundary to automatically retry, re-rendering the component tree when specific props or state values change. Here’s an example demonstrating how you can implement a retry mechanism using react-error-boundary:

import { ErrorBoundary } from 'react-error-boundary'

function ErrorFallback({ error, resetErrorBoundary }) {
return (
<div role="alert">
<p>Something went wrong:</p>
<pre>{error.message}</pre>
<button onClick={resetErrorBoundary}>Try again</button>
</div>
)
}

function MyComponent({ retryCount }) {
// Some component logic that may throw JS errors
}

function App() {
const [retryCount, setRetryCount] = React.useState(0)

return (
<ErrorBoundary
FallbackComponent={ErrorFallback}
onReset={() => setRetryCount(retryCount + 1)} // increment the retry count on reset
resetKeys={[retryCount]} // reset the error boundary when `retryCount` changes
>
<MyComponent retryCount={retryCount} />
</ErrorBoundary>
)
}

In this example, the App component maintains a retryCount state. When the "Try again" button is clicked in the ErrorFallback component, it calls resetErrorBoundary. This then triggers the onReset and then clears the error state.

The onReset increments the retryCount, which then causes the error boundary to reset due to the change in resetKeys, resulting in MyComponent being rendered again. Catching all errors and providing effective retry mechanisms is essential for building robust and resilient React applications, and react-error-boundary provides the tools you need to achieve this.

Common design patterns for implementing error boundaries

Design patterns for implementing error boundaries in React applications offer varying levels of granularity and are chosen based on specific use cases and application architectures. Let’s explore three common patterns:

  1. Component-level error boundaries:
  • This pattern involves wrapping individual components in error boundaries, providing a high level of granularity.
  • When a component encounters an error, the boundary catches it, preventing it from propagating up the component tree.
  • Component-level error boundaries are ideal for isolated components that do not share state, ensuring that a failure in one component does not impact others.
  • However, this approach may lead to code duplication if many components require their own error boundaries.

2. Layout-level error boundaries:

  • Layout-level error boundaries are placed higher in the component tree, typically wrapping groups of related components.
  • They are suitable for closely related components that share a common state.
  • When an error occurs within a group of components, the layout-level boundary catches it and can display an error message or fallback UI for the entire group.
  • Despite providing a broader scope of error handling, layout-level boundaries are less granular and may impact the entire group of components even if only one encounters an error.

3. Top-level error boundaries:

  • Placed at the highest level of the component tree, top-level error boundaries serve as catch-all solutions for handling errors across the application.
  • They ensure that any error occurring within the application is caught and handled gracefully, preventing the entire application from crashing.
  • While offering comprehensive error handling, top-level boundaries lack granularity, as an error can affect the entire application rather than specific components or groups.

Each pattern has its advantages and considerations, and the choice depends on factors such as the level of isolation between components, the desired scope of error handling, and the complexity of the application. By selecting the appropriate design pattern, developers can effectively manage errors and enhance the resilience of React applications.

Conclusion:

Whether you’re working with class components or embracing the functional paradigm, react-error-boundary offers a versatile toolkit for integrating error handling seamlessly into your React applications. Its flexible API, which includes components, higher-order components, and custom Hooks, provides multiple avenues to incorporate error management into your components effectively.

With react-error-boundary, you gain access to features like custom fallback UIs, error reset functionality, and error reporting, enhancing the user experience by ensuring smooth handling of errors. Whether it’s displaying informative error messages or gracefully recovering from errors, react-error-boundary empowers you to maintain a polished and reliable application.

By integrating react-error-boundary into your React projects, you can streamline error handling, simplify debugging processes, and ultimately deliver a higher-quality end product to your users. Spend less time fretting over error management and more time crafting exceptional features that delight your audience. Explore the capabilities of react-error-boundary on GitHub and elevate your React development experience today.

--

--

Agam Kakkar

Lead Software Engineer | Full Stack Developer | JAVA | React JS