Frontend Development 19 min read

Mastering React Error Boundaries: Build a Flexible Error‑Handling Wheel

This tutorial walks through why catching front‑end component errors is essential, demonstrates how to implement a custom React ErrorBoundary with flexible fallback options, reset logic, higher‑order component wrappers, and hooks, and provides complete TypeScript examples and best‑practice summaries.

Tencent IMWeb Frontend Team
Tencent IMWeb Frontend Team
Tencent IMWeb Frontend Team
Mastering React Error Boundaries: Build a Flexible Error‑Handling Wheel

What Happened?

A front‑end developer receives a panic call from the boss: the page went white. After opening the console, the developer sees a response from the back‑end that contains a

retcode

and a

data

array with a valid

User

object, an

undefined

entry and a

null

entry. The developer initially filters out falsy values and thinks everything is fine, but later discovers that the back‑end returned an unexpected string "User not found" for some items, causing the UI to crash.

The root cause is the lack of proper type checking and error handling on the front‑end side. The correct way to handle such component exceptions in React is to use an Error Boundary .

Step 1: Copy the Official Example

Copy the example from the React documentation and output an

ErrorBoundary

component:

<code>class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }
  static getDerivedStateFromError(error) {
    // Update state so the next render shows the fallback UI
    return { hasError: true };
  }
  componentDidCatch(error, errorInfo) {
    // You can also log the error to an error reporting service
    logger.error(error, errorInfo);
  }
  render() {
    if (this.state.hasError) {
      // You can render any custom fallback UI
      return <h1>Something went wrong.</h1>;
    }
    return this.props.children;
  }
}
</code>

Wrap the business component with the boundary:

<code>&lt;ErrorBoundary&gt;
  &lt;UserList /&gt;
&lt;/ErrorBoundary&gt;
</code>

When

UserList

throws,

getDerivedStateFromError

updates the state and the fallback UI is displayed instead of the broken component.

Step 2: Build a Flexible Wheel

Instead of hard‑coding the fallback UI, expose three ways to provide a fallback:

fallback

– a React element.

FallbackComponent

– a component that receives the error as a prop.

fallbackRender

– a render function that receives

FallbackProps

.

<code>type FallbackElement = React.ReactElement | null;
interface FallbackProps { error: Error; }
interface ErrorBoundaryProps {
  fallback?: FallbackElement;
  FallbackComponent?: React.ComponentType&lt;FallbackProps&gt;;
  fallbackRender?: (props: FallbackProps) => FallbackElement;
  onError?: (error: Error, info: string) => void;
}
class ErrorBoundary extends React.Component&lt;React.PropsWithChildren&lt;ErrorBoundaryProps&gt;, { error: Error | null }&gt; {
  static getDerivedStateFromError(error) { return { error }; }
  componentDidCatch(error, errorInfo) { this.props.onError?.(error, errorInfo.componentStack); }
  render() {
    const { fallback, FallbackComponent, fallbackRender, children } = this.props;
    const { error } = this.state;
    if (error) {
      if (React.isValidElement(fallback)) return fallback;
      if (fallbackRender) return fallbackRender({ error });
      if (FallbackComponent) return &lt;FallbackComponent error={error} /&gt;;
      throw new Error('ErrorBoundary requires one of fallback, fallbackRender, or FallbackComponent');
    }
    return children;
  }
}
</code>

Example usage with the three props:

<code>// Using fallback element
&lt;ErrorBoundary fallback=&lt;div>Something went wrong&lt;/div>&gt;&lt;UserList /&gt;&lt;/ErrorBoundary&gt;

// Using a component
&lt;ErrorBoundary FallbackComponent={ErrorFallback}&gt;&lt;UserList /&gt;&lt;/ErrorBoundary&gt;

// Using a render function
&lt;ErrorBoundary fallbackRender={(props) => &lt;ErrorFallback {...props} />}&gt;&lt;UserList /&gt;&lt;/ErrorBoundary&gt;
</code>

Step 3: Add Reset Callback

Sometimes the error is temporary (e.g., a 502 response). Provide a

onReset

prop and expose

resetErrorBoundary

to the fallback so the user can retry without a full page reload.

<code>const renderFallback = (props) => (
  &lt;div&gt;
    Something went wrong, you can &lt;button onClick={props.resetErrorBoundary}&gt;reset&lt;/button&gt;.
  &lt;/div&gt;
);

&lt;ErrorBoundary fallbackRender={renderFallback} onReset={() => console.log('reset')} onError={logger.error}&gt;
  &lt;UserList /&gt;
&lt;/ErrorBoundary&gt;
</code>

Step 4: Listen to Render for Automatic Reset

Introduce a

resetKeys

array prop. When any value in the array changes, the boundary automatically resets. Also provide

onResetKeysChange

for custom change detection.

<code>function changedArray(a = [], b = []) {
  return a.length !== b.length || a.some((item, i) => !Object.is(item, b[i]));
}

class ErrorBoundary extends React.Component {
  componentDidUpdate(prevProps) {
    if (changedArray(prevProps.resetKeys, this.props.resetKeys)) {
      this.props.onResetKeysChange?.(prevProps.resetKeys, this.props.resetKeys);
      this.reset();
    }
  }
  // reset() clears the error state
}
</code>

To avoid resetting while the component is still rendering the error UI, a flag

updatedWithError

is used to differentiate the first render caused by the error from subsequent renders.

Step 5: Export a Higher‑Order Component

Wrap any component with

withErrorBoundary

to avoid repetitive JSX:

<code>function withErrorBoundary(Component, errorBoundaryProps) {
  const Wrapped = (props) => (
    &lt;ErrorBoundary {...errorBoundaryProps}&gt;
      &lt;Component {...props} /&gt;
    &lt;/ErrorBoundary&gt;
  );
  const name = Component.displayName || Component.name || 'Component';
  Wrapped.displayName = `withErrorBoundary(${name})`;
  return Wrapped;
}

const UserWithErrorBoundary = withErrorBoundary(User, { onError: () => logger.error('error'), onReset: () => console.log('reset') });
</code>

Step 6: Provide a Hook for Manual Error Throwing

The

useErrorHandler

hook lets developers throw errors from inside hooks or event handlers so that an outer

ErrorBoundary

can catch them.

<code>function useErrorHandler(givenError) {
  const [error, setError] = React.useState(null);
  if (givenError) throw givenError;
  if (error) throw error;
  return setError;
}

function Greeting() {
  const [greeting, setGreeting] = React.useState(null);
  const handleError = useErrorHandler();
  const handleSubmit = (e) => {
    e.preventDefault();
    const name = e.target.elements.name.value;
    fetchGreeting(name).then(setGreeting, handleError);
  };
  return greeting ? <div>{greeting}</div> : (
    <form onSubmit={handleSubmit}>
      <label>Name</label>
      <input id="name" />
      <button type="submit">Get greeting</button>
    </form>
  );
}
export default withErrorBoundary(Greeting);
</code>

Final Summary

Implemented a reusable

ErrorBoundary

component.

Used

componentDidCatch

and

getDerivedStateFromError

to capture errors and store the

Error

object.

Provided three ways to render fallback UI:

fallback

,

FallbackComponent

, and

fallbackRender

.

Added reset capabilities via

onReset

,

resetErrorBoundary

, and the

resetKeys

prop.

Implemented automatic reset when

resetKeys

changes, with optional

onResetKeysChange

for custom logic.

Offered a higher‑order component

withErrorBoundary

and a hook

useErrorHandler

for flexible integration.

All code is available on GitHub: https://github.com/Haixiang6123/learn-error-bounday Reference library: https://www.npmjs.com/package/react-error-boundary
frontendTypeScriptreacthookserror handlingErrorBoundaryHigher-Order Component
Tencent IMWeb Frontend Team
Written by

Tencent IMWeb Frontend Team

IMWeb Frontend Community gathering frontend development enthusiasts. Follow us for refined live courses by top experts, cutting‑edge technical posts, and to sharpen your frontend skills.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.