React · State at Scale

Error Boundaries

One bug in one widget should not turn the whole page white. Error boundaries catch errors in their part of the tree and show a fallback, while the rest of the app keeps working.

Advanced 6 min Lesson 31 of 42Examples target React 19 with Vite.
Before you start:Components
0/42 in React
On this page · 6 min · Advanced

Learning objectives#

  • Explain what error boundaries catch
  • Write a class error boundary
  • Use react-error-boundary with reset
  • Place boundaries at sensible levels

Explanation#

If a component throws while rendering, React unmounts the whole tree — users see a blank page. An error boundary stops that at a chosen point.

An error boundary is a class component with static getDerivedStateFromError (to show the fallback) and componentDidCatch (to log the error). There is no hook equivalent yet.

Most projects use the react-error-boundary package: <ErrorBoundary FallbackComponent={...} onReset={...}> with a resetErrorBoundary function for “Try again”.

Boundaries catch errors in rendering, lifecycle methods and constructors below them. They do NOT catch errors in event handlers, async code (setTimeout, fetch promises) or the boundary itself — handle those with try/catch and error state.

Place one boundary around the whole app (last resort) and others around independent widgets and routes, so a broken chart does not take down navigation.

Syntax#

JSX Illustrative
class ErrorBoundary extends Component {
  state = { error: null };
  static getDerivedStateFromError(error) { return { error }; }
  componentDidCatch(error, info) { logError(error, info.componentStack); }
  render() { return this.state.error ? this.props.fallback : this.props.children; }
}

Code example: A broken widget that does not break the page#

JSXDashboard.jsx Runnable
import { Component, useState } from 'react';


class ErrorBoundary extends Component {
  state = { error: null };


  static getDerivedStateFromError(error) {
    return { error };                                   // switch to the fallback
  }


  componentDidCatch(error, info) {
    console.error('Widget crashed:', error.message, info.componentStack);   // send to logging here
  }


  reset = () => this.setState({ error: null });


  render() {
    if (this.state.error) {
      return (
        <div role="alert" style={{ border: '1px solid #dc2626', padding: 8 }}>
          <p>{this.props.title} could not be displayed.</p>
          <button onClick={this.reset}>Try again</button>
        </div>
      );
    }
    return this.props.children;
  }
}


function RevenueWidget({ data }) {
  // Bug: crashes when data is missing
  return <p>Revenue: ₹{data.total.toLocaleString('en-IN')}</p>;
}


export default function Dashboard() {
  const [broken, setBroken] = useState(true);
  return (
    <>
      <nav>Home · Orders · Reports</nav>
      <ErrorBoundary title="Revenue">
        <RevenueWidget data={broken ? null : { total: 125000 }} />
      </ErrorBoundary>
      <ErrorBoundary title="Orders">
        <p>Orders today: 42</p>
      </ErrorBoundary>
      <button onClick={() => setBroken(false)}>Fix the data</button>
    </>
  );
}
Expected output
The navigation and “Orders today: 42” render normally. The revenue widget shows a red box “Revenue could not be displayed.” and the console logs “Widget crashed: Cannot read properties of null (reading ‘total’)”. Clicking “Fix the data” then “Try again” shows “Revenue: ₹1,25,000”.

This example is illustrative. Copy or download it and run it in your own environment; the expected output is shown above.

Step-by-step explanation#

  1. RevenueWidget throws while rendering because data is null.
  2. getDerivedStateFromError stores the error, so the boundary renders its fallback.
  3. componentDidCatch logs the error and component stack.
  4. Only the Revenue boundary is affected; navigation and the Orders widget keep working.
  5. reset clears the error so the children render again once the data is fixed.

Real-world use case#

A news site wraps each homepage section in a boundary. When a third-party weather widget starts throwing, only that box shows “Weather unavailable”, and the error is reported to Sentry.

Common mistakes#

Expecting boundaries to catch event-handler errors

Errors thrown in onClick or in a fetch promise are not caught. Use try/catch and error state there.

One boundary at the top only

Any error replaces the entire app with the fallback. Add boundaries around independent sections.

No logging

Fallbacks hide errors from developers. Report them in componentDidCatch (or onError).

Best practices#

  • A top-level boundary plus boundaries per widget or route
  • Log errors to a monitoring service
  • Offer a retry or reload
  • Use react-error-boundary in real projects

Remember this#

Boundary = try/catch for rendering.

Not caught: event handlers, async code, the boundary itself.

Small boundaries keep the rest of the page alive.

Practice exercise#

Your task: Write a Profile component that throws if user.name is missing, and wrap it in an ErrorBoundary with fallback text "Profile unavailable". Render it with a user that has no name.

Need a hint?
Reuse the class boundary; pass the fallback as a prop.
Show solution
JSX Runnable
import { Component } from 'react';


class Boundary extends Component {
  state = { failed: false };
  static getDerivedStateFromError() { return { failed: true }; }
  render() { return this.state.failed ? <p role="alert">{this.props.fallback}</p> : this.props.children; }
}


function Profile({ user }) {
  if (!user.name) throw new Error('User has no name');
  return <h2>{user.name}</h2>;
}


export default function App() {
  return (
    <Boundary fallback="Profile unavailable">
      <Profile user={{}} />
    </Boundary>
  );
}

Mini quiz#

Error Boundaries quizMultiple choice Question 1 of 3

Which error is caught by an error boundary?

Score so far: 0/0

Want more? Practise all React MCQs · React interview questions

My notes#

Saved in this browser.

Progress is saved in this browser. Sign in to sync.