iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Error Boundaries

An error boundary is a class component that catches render-time errors in its subtree and displays a fallback UI. React deliberately did not ship a hook for this — it relies on componentDidCatch and getDerivedStateFromError. Without one, a render error in any child unmounts the whole React tree, leaving the user staring at a blank page.

A reusable ErrorBoundary with reset and reporting

EXAMPLE
import React from 'react';

// 1) The boundary itself — class component is the only option today
class ErrorBoundary extends React.Component {
  state = { error: null };

  static getDerivedStateFromError(error) {
    return { error };
  }

  componentDidCatch(error, info) {
    // Send to Sentry/Bugsnag/your logger. Never throw from here.
    if (this.props.onError) this.props.onError(error, info);
    console.error('[ErrorBoundary]', error, info.componentStack);
  }

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

  render() {
    if (this.state.error) {
      const Fallback = this.props.fallback ?? DefaultFallback;
      return <Fallback error={this.state.error} reset={this.reset} />;
    }
    return this.props.children;
  }
}

function DefaultFallback({ error, reset }) {
  return (
    <div role='alert' style={{ padding: 16, border: '1px solid #f33' }}>
      <h2>Something went wrong</h2>
      <pre style={{ whiteSpace: 'pre-wrap' }}>{error.message}</pre>
      <button onClick={reset}>Try again</button>
    </div>
  );
}

// 2) Place boundaries at meaningful seams — per-route, per-widget — not at the root
function Dashboard() {
  return (
    <Layout>
      <ErrorBoundary fallback={WidgetFallback}>
        <RevenueChart />
      </ErrorBoundary>
      <ErrorBoundary fallback={WidgetFallback}>
        <ActivityFeed />
      </ErrorBoundary>
    </Layout>
  );
}

function WidgetFallback({ error, reset }) {
  return (
    <div className='widget-fallback'>
      <p>This widget crashed.</p>
      <button onClick={reset}>Reload widget</button>
    </div>
  );
}

// 3) What boundaries DO NOT catch:
//    - Event handlers (use try/catch inside the handler)
//    - Async code, promises (use .catch and surface via state)
//    - Server-side rendering errors (handle on the server)
//    - Errors in the boundary itself

// 4) Reporting hook used at the top of the tree
function reportToSentry(error, info) {
  if (typeof window !== 'undefined' && window.Sentry) {
    window.Sentry.captureException(error, { extra: { componentStack: info.componentStack } });
  }
}

export default function App() {
  return (
    <ErrorBoundary onError={reportToSentry}>
      <Dashboard />
    </ErrorBoundary>
  );
}

Why it matters

Place boundaries at boundaries — per route, per widget, per third-party embed — not just at the root. A single top-level boundary still wipes the screen on any crash; nested boundaries let the surviving parts of the page keep working while the broken one shows a recoverable fallback.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
class Boundary extends Component {
    state = { error: null };
    static getDerivedStateFromError(error) { return { error }; }
    render() { return this.state.error ? <Fallback /> : this.props.children; }
}
Try it Yourself »

Discussion

Loading…