Skip to content
matthewcm.dev

    ↑ ↓ move · ↵ open · tab scopeopen graph →

    React Error Handling

    Split failures into render errors, caught by error boundaries with a fallback, and network errors, caught with try/catch and retry. Make sure both paths end in a logged error and a usable screen.

    Seedtended planted 1 min read

    🌱 A rough idea, planted recently. Expect rough edges — it will grow.

    On this page

    Split failures into UI (render) errors, caught by error boundaries, and network errors, caught with try/catch plus retry. Then make sure both paths end in a logged error and a screen the user can still work with.

    The flow

    A user action can fail in two ways:

    • The UI fails. A component throws, an ErrorBoundary catches it and shows a fallback (or cached data). The error also reaches a global handler, which logs it.
    • A network request fails. try/catch catches it and a retry mechanism backs off and tries a few more times. If it still fails, the error is logged and surfaced to the user.

    Error boundaries

    • They catch errors during rendering, in lifecycle methods and in constructors below them. They don’t catch errors in event handlers, async code (setTimeout, promises you don’t render), server rendering, or the boundary itself. Handle those with try/catch or the global handler.
    • The easy route is react-error-boundary:
    <ErrorBoundary FallbackComponent={Fallback} onError={log} resetKeys={[orderId]}>
      <OrderPanel id={orderId} />
    </ErrorBoundary>
    • Place boundaries per feature or panel so one broken widget doesn’t blank the whole app, and keep one at the root as a last resort.
    • use(promise) and Suspense data fetching send rejections to the nearest boundary (React 19 Hooks Cheat Sheet); TanStack Query’s throwOnError does the same.

    The global handler and logging

    • window.addEventListener("error") and "unhandledrejection" catch what boundaries miss. Forward both to your error tracker or RUM tool (Observability).
    • try/catch only catches a rejected promise if you await it.

    Network errors and retry

    • Retry only idempotent requests, with exponential backoff and jitter. TanStack Query retries three times by default.
    • Give users distinct states: offline, 4xx (fix your input), 5xx (try again) and timeout (AbortSignal.timeout; see React Data Fetching Patterns).
    • Race conditions between responses are a data-fetching bug, not an error. Cancel stale requests instead.
    • #react
    • #error-boundaries
    • #error-handling
    • #observability
    • #resilience

    ↪ Links to (3)