ErrorBoundary

Catch and handle React component errors with declarative error boundaries.

The ErrorBoundary component catches JavaScript errors anywhere in its child component tree, logs the error, and displays a fallback UI instead of crashing the entire application.

Installation

npm install @zayne-labs/ui-react

Preview

Loading...

Basic Usage

Provide a static component or element to display when an error occurs.

import { ErrorBoundary } from "@zayne-labs/ui-react/common/error-boundary";

function App() {
  return (
    <ErrorBoundary fallback={<div className="p-4 border border-red-200 bg-red-50 text-red-700">Something went wrong</div>}>
      <ComponentThatMightCrash />
    </ErrorBoundary>
  );
}

Use a render function to access the error details and provide a way to reset the boundary.

import { ErrorBoundary } from "@zayne-labs/ui-react/common/error-boundary";

function App() {
  return (
    <ErrorBoundary
      fallback={({ error, resetErrorBoundary }) => (
        <div className="flex flex-col gap-4 p-8 border rounded-lg">
          <h2 className="text-xl font-bold">Unexpected Error</h2>
          <code className="text-sm p-2 bg-gray-100 rounded">{error.message}</code>
          <button
            onClick={resetErrorBoundary}
            className="rounded-md bg-zu-primary px-4 py-2 text-white"
          >
            Try Again
          </button>
        </div>
      )}
    >
      <ComponentThatMightCrash />
    </ErrorBoundary>
  );
}

Component Source

"use client";import { isFunction } from "@zayne-labs/toolkit-type-helpers";import { Component } from "react";import { ErrorBoundaryContext } from "./error-boundary-context";import type { ErrorBoundaryProps } from "./types";type ErrorBoundaryState =	| {			error: Error;			hasError: true;	  }	| {			error: null;			hasError: false;	  };const initialState: ErrorBoundaryState = {	error: null,	hasError: false,};const hasArrayChanged = (arrayOne: unknown[] = [], arrayTwo: unknown[] = []) => {	return (		arrayOne.length !== arrayTwo.length		|| arrayOne.some((item, index) => !Object.is(item, arrayTwo[index]))	);};/** * Copied from react-error-boundary package * @see https://github.com/bvaughn/react-error-boundary */export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {	constructor(props: ErrorBoundaryProps) {		super(props);		this.state = initialState;	}	static getDerivedStateFromError(error: Error) {		return { error, hasError: true };	}	override componentDidCatch(error: Error, info: React.ErrorInfo) {		this.props.onError?.({ error, info });	}	override componentDidUpdate(prevProps: ErrorBoundaryProps, prevState: ErrorBoundaryState) {		const { hasError } = this.state;		const { errorResetKeys } = this.props;		// == There's an edge case where if the thing that triggered the error happens to *also* be in the resetKeys array, we'd end up resetting the error boundary immediately.		// == This would likely trigger a second error to be thrown.		// == So we make sure that we don't check the resetKeys on the first call of cDU after the error is set.		if (			hasError			&& prevState.error !== null			&& hasArrayChanged(prevProps.errorResetKeys, errorResetKeys)		) {			this.props.onErrorReset?.({				next: errorResetKeys,				prev: prevProps.errorResetKeys,				reason: "keys",			});			this.setState(initialState);		}	}	override render() {		const { children, errorFallback } = this.props;		const { error, hasError } = this.state;		let childToRender = children;		if (hasError) {			switch (true) {				case isFunction(errorFallback): {					childToRender = errorFallback({						error,						resetErrorBoundary: this.#resetErrorBoundary,					});					break;				}				case Boolean(errorFallback): {					childToRender = errorFallback;					break;				}				default: {					console.warn("No fallback provided to error boundary");				}			}			return (				<ErrorBoundaryContext					value={{						error,						hasError,						resetErrorBoundary: this.#resetErrorBoundary,					}}				>					{childToRender}				</ErrorBoundaryContext>			);		}		return childToRender;	}	#resetErrorBoundary = (...parameters: unknown[]) => {		const { error } = this.state;		if (error === null) return;		this.props.onErrorReset?.({			args: parameters,			reason: "imperative-api",		});		this.setState(initialState);	};}

Component API

Best Practices

  • Isolate Failures: Place boundaries around specific widgets or sections (like a sidebar or a feed) to prevent a single component failure from breaking the whole page.
  • Auto-Reset: Use resetKeys with URL params or resource IDs so that navigating away and back "fixes" the component if it crashed.
  • Global Boundary: Keep a generic boundary at the very top of your app as a last resort.

Error boundaries do not catch errors in event handlers or asynchronous code (like setTimeout) by default. Use the showBoundary method from useErrorBoundary to catch these manually.

Edit on GitHub

Last updated on

On this page