DesignDev.io logoDesignDev.io logo

  1. Home
  2. /React Foundations
  3. /The Complete Guide to React Error Boundaries
← Back to home

The Complete Guide to React Error Boundaries

Alex Chen

Alex Chen

Cover Image for The Complete Guide to React Error Boundaries
Alex Chen

Alex Chen

January 9, 2026·5 min read
Updated April 26, 2026
Series:React Foundations
Level:Intermediate
Tags:reactweb
Assumes:Comfortable with React components and hooks, basic understanding of error handling

Error boundaries are one of the most underused React features in production codebases. Most apps either wrap everything in one global boundary or use none at all. Here's the granular placement strategy that actually makes them useful — and the patterns that work.


Something will go wrong in your React app. A component will try to read a property on undefined. An API response will have an unexpected shape. A third-party library will throw during render. This is not pessimism — it's the nature of software running in a browser with real data from real users.

The question isn't whether errors happen. It's whether your app handles them gracefully or shows a blank white screen.

Error boundaries are React's answer. Most developers know they exist. Far fewer use them effectively.


What error boundaries catch

Error boundaries catch errors that occur during:

  • Rendering — a component throws while React is building the UI
  • Lifecycle methods — componentDidMount, componentDidUpdate
  • Constructors of child components

They do not catch errors in:

  • Event handlers — onClick, onChange, and friends. Use try/catch there
  • Async code — setTimeout, fetch, Promises. These run outside the render cycle
  • Server-side rendering — boundaries only work in the browser
  • The error boundary itself — a boundary can't catch its own errors

This distinction matters. A component that fetches data and throws during the fetch won't be caught by an error boundary — the fetch happens asynchronously. A component that throws while rendering the fetched data will be caught.


The class component reality

Error boundaries must be class components. React has not shipped a hook equivalent — useErrorBoundary doesn't exist in React core. This is a long-standing limitation that the React team has acknowledged but not yet resolved.

Writing a class component today feels like putting on a suit to mow the lawn. But for error boundaries, it's still the only native option:

Two lifecycle methods do the work:

getDerivedStateFromError — automatically called when a child throws. Returns new state that tells the boundary to show the fallback. Static — no access to this.

componentDidCatch — called after getDerivedStateFromError. Receives the error and a component stack trace. This is where you call Sentry, Datadog, or whatever error tracking service you use.


Skip the class component — use react-error-boundary

The react-error-boundary library wraps the class component boilerplate and exposes a clean API that feels like modern React:

The resetErrorBoundary function clears the error state and re-renders the children — no key-changing trick required. onError replaces componentDidCatch. Clean, typed, and no class components in your codebase.


Granular placement: the strategy that matters

Here's the mistake most codebases make. One error boundary wrapping the entire app:

When any component anywhere in the tree throws, the entire app disappears and the user sees a generic error message. They've lost their current context, their scroll position, any unsaved input — everything.

The better strategy: boundaries at the feature level, not the app level.

If AnalyticsWidget throws, the user still sees the activity feed and the revenue chart. The error is contained. The rest of the page is intact.

This is the same philosophy as microservice isolation applied to the component tree: a failure in one subsystem shouldn't take down the whole system.


The reset function and when to use it

React's default behaviour when an error boundary catches an error: show the fallback and stay there. The user has to manually refresh to try again.

react-error-boundary's resetErrorBoundary gives users a retry path without a full page reload. But reset alone isn't always enough — if the error was caused by state that's still wrong, resetting the boundary will just throw again immediately.

The resetKeys prop handles this: when any value in the resetKeys array changes, the boundary resets automatically.

When the user navigates to a different profile, userId changes, the boundary resets, and ProfileContent gets a fresh render with the new userId. No manual retry button needed — the error clears naturally when the context changes.


Logging errors to Sentry

The onError callback (or componentDidCatch in a custom class boundary) is where external error logging goes. With Sentry:

Sentry also ships its own Sentry.ErrorBoundary component that wraps react-error-boundary with automatic capture built in:

The showDialog prop opens Sentry's user feedback dialog when an error is caught — letting users describe what they were doing when it broke. Surprisingly useful for debugging intermittent issues.


Error boundaries with Suspense

Error boundaries and Suspense compose naturally — and in the App Router, they're often used together. Suspense handles the loading state, the error boundary handles the failure state.

The order matters: error boundary wraps Suspense, not the other way around. If the suspended data fetch fails and throws, the error boundary catches it. If Suspense wrapped the boundary, a throw inside Suspense would suspend indefinitely rather than being caught.


Development vs production

In development, React intentionally re-throws errors after an error boundary catches them — so they appear in the browser console and the React error overlay. This can look like the boundary isn't working when it is.

In production, React catches the error, renders the fallback, and stays quiet. The error boundary is doing its job — you just don't see it unless you check your error tracking dashboard.

Don't mistake the development overlay for a boundary failure. Test error boundary behaviour in a production build or with process.env.NODE_ENV === 'production' explicitly set.


The placement checklist

When deciding where to put an error boundary, ask:

If this component throws, what's the minimum UI that should survive?

The answer tells you where the boundary goes. If a sidebar widget throws and the main content should still show — boundary wraps the widget. If an entire route's data fetch fails and the whole page is unusable — boundary wraps the route.

At minimum, every app should have:

  1. A root-level boundary — catches anything that slips through everything else. Last resort, generic message.
  2. Route-level boundaries — one per major route. Isolates page-level failures from the navigation chrome.
  3. Feature-level boundaries — around independent widgets, data-heavy components, and third-party integrations.

Error boundaries aren't glamorous. They're the part of the codebase nobody thinks about until production breaks. But an app with well-placed boundaries fails gracefully — users see a contained error message with a retry option, not a blank screen with no path forward.

That difference is worth the thirty minutes it takes to add them properly.


Up next: Controlled vs uncontrolled components — the decision you keep making wrong — the React form pattern that determines whether you fight your inputs or work with them.

Related: Next.js error handling — error.tsx, global-error.tsx, and when to use each — how error boundaries translate to the App Router's file-based conventions.

Alex Chen is a senior frontend engineer who writes about React patterns, JavaScript internals, and the decisions that separate maintainable codebases from ones that fight back. Opinionated by design.

Photo by Stanisław Krawczyk on Unsplash


More Stuff

Publishing Your First React Component to npm — the Complete Setup
April 1, 2026·3 min read

Publishing Your First React Component to npm — the Complete Setup

Most tutorials stop at "it works on my machine." This one doesn't. Publishing to npm is where your library becomes real — installable, versioned, and usable by anyone. Here's the complete setup, including the fields most developers get wrong in package.json and how to inspect what you're actually shipping before it goes live.

Muhammad Athar

Muhammad Athar

How to structure a UI library that scales — folders, exports, and naming conventions
April 1, 2026·3 min read

How to structure a UI library that scales — folders, exports, and naming conventions

The folder structure you pick on day one is the one you'll live with across 40 components. Get it wrong and you'll be fighting your own codebase by article 10. Get it right and adding a new component takes five minutes and follows a pattern every contributor can learn in one sitting.

Muhammad Athar

Muhammad Athar

DesignDev.io logoDesignDev.io logo

Explore

  • Home
  • Search
  • Authors
  • Series
  • About Us

Account

  • Sign in

Legal

  • Privacy Policy
  • Terms of Service

© 2026 DesignDev.io · All rights reserved

Built with Next.js · Tailwind · Sanity · Vercel