Stockio

React Error Boundary and Suspense for Resilient UIs

Design React loading and failure states that preserve useful content, isolate broken widgets, and offer reliable recovery without turning every render into a full-page spinner.

By Daniel Kim

React Developer ·

On this page

A dashboard should remain useful when its activity feed is slow or its recommendations fail. React gives these situations different mechanisms: Suspense coordinates supported pending work, while error boundaries replace a subtree after a rendering failure. Treating both as one generic loading flag produces confusing recovery paths and unnecessarily empty screens.

Start with independent regions

Sketch the page as regions users can understand independently: account summary, recent invoices, and optional recommendations. Give each region a clear pending appearance and a failure message. Use the contrast checker to verify that muted skeleton labels, retry buttons, and error copy remain readable against their actual backgrounds. Loading states are still interface states, not decorative exceptions.

Choose boundary placement according to tasks rather than component count. An invoice form and its validation summary usually belong together; a marketing recommendation beside that form should not be able to replace it. Keep navigation and the page heading outside optional regions so users retain their location when something breaks.

Write down what remains usable during each failure. This simple exercise reveals where a full-page fallback is inappropriate. For broader layout decisions, the development hub provides a useful starting point, but resilience begins with product-specific expectations: can someone still download an existing invoice when recommendations are unavailable?

Distinguish pending work from errors

Suspense is not a fetch detector. A request started inside an ordinary effect does not automatically activate the nearest Suspense boundary. Supported sources include lazy component imports and integrations that explicitly support suspending reads. Follow the React Suspense documentation and your framework or data library rather than inventing a promise cache during render.

An error boundary catches errors thrown while rendering descendants and in supported React lifecycle work. It does not generally catch event-handler exceptions, arbitrary asynchronous callbacks, server rendering errors, or errors in its own fallback. A failed save initiated by a click therefore needs explicit handling in the mutation flow, often as a form-level message.

A request failure can reach an error boundary when a Suspense-aware data layer throws that error during render. That is an integration behavior, not a universal property of fetch. Likewise, a missing record might deserve a dedicated not-found screen rather than the same recovery message used for a temporary service outage.

Use expected failures as data whenever possible. Invalid input, permission-denied responses, and declined payments usually need specific instructions. Unexpected rendering failures belong in a containment mechanism with safe diagnostics and a reasonable escape route.

Compose boundaries around meaningful content

The following example uses the third-party react-error-boundary package and React's lazy loading. The lazy component import supplies a real Suspense source, making the example independent of any particular data-fetching library.

import { lazy, Suspense } from "react";
import { ErrorBoundary } from "react-error-boundary";

const ActivityFeed = lazy(() => import("./ActivityFeed"));

export function ActivityRegion() {
return (
  <section aria-labelledby="activity-title">
    <h2 id="activity-title">Recent activity</h2>
    <ErrorBoundary
      fallbackRender={() => (
        <p>
          Activity could not load. Save your work, then reload
          the page to try again.
        </p>
      )}
    >
      <Suspense fallback={<p role="status">Loading activity…</p>}>
        <ActivityFeed />
      </Suspense>
    </ErrorBoundary>
  </section>
);
}

Here, an import that is still pending shows the loading message; a rejected import reaches the error boundary. The heading stays visible in either case. A production implementation can add telemetry through the boundary's error callback, provided it removes credentials and personal data before transmission.

Avoid promising that resetting this boundary will retry a rejected lazy import. React caches the lazy loader's promise and result. Recovery may require a page reload or deployment-aware asset recovery, and reloading without warning can destroy unsaved work. Read React's lazy reference before building a universal retry button.

Preserve continuity while loading

A large boundary can coordinate a region that must appear together, while nested boundaries can reveal independent sections progressively. Neither arrangement is inherently better. Choose based on whether partial content helps people act or instead creates misleading combinations of old and new information.

For subsequent navigation, transitions can help preserve already revealed content instead of immediately replacing it with a fallback. They do not eliminate pending work, and their behavior depends on the update and boundary structure. Consult useTransition before marking updates nonurgent; typing into a controlled input must remain responsive and urgent.

Reserve plausible space for pending content, but avoid skeletons that suggest exact data you cannot predict. Keep status announcements short. Repeated nested live announcements can become more disruptive than a plain message. The accessible interface guide is useful when reviewing keyboard paths and announcements across these states.

Treat stale content as an explicit product choice. Keeping yesterday's balance visible while fetching today's value may preserve context, but users need a clear updating indicator and safeguards against acting on outdated information. A spinner alone does not explain whether the visible information is current.

Make recovery specific and testable

For query failures, retry semantics should include both clearing the boundary state and resetting or refetching the failed query through the data library. For a deterministic rendering bug, retrying the same inputs usually reproduces the crash. Offer an alternative route or support reference instead of encouraging endless clicks.

Test a slow response, a rejected request, a malformed response, and a component that deliberately throws. Verify each at both initial entry and client-side navigation. Also test when another region contains unsaved input. The central question is not merely whether the fallback appears, but whether unaffected work survives.

Add failure observability with boundary identity, route, release identifier, and a sanitized error category. Avoid logging entire response bodies or form values. Correlate repeated failures without exposing sensitive content in messages shown to users.

Finally, review the recovery copy with someone who did not implement the feature. They should understand what failed, what still works, and what the next action risks. Record these scenarios alongside your component examples so later refactors preserve the intended behavior rather than merely preserving the happy-path screenshot.

Key takeaways

  • Use task-sized boundaries to isolate optional failures without hiding navigation or unrelated work.
  • Suspense requires supported pending sources; ordinary effect-based fetching does not activate it automatically.
  • Handle event and mutation failures explicitly instead of expecting render boundaries to catch everything.
  • Keep recovery honest: query resets, asset reloads, and deterministic crashes need different responses.
  • Test pending, failed, stale, and recovered states with keyboard navigation and unsaved work present.
Featured sponsor

Reach designers & developers at work

Put your product next to the tools people use every day. One sponsor per placement, no tracking pixels.

Become a sponsor

Keep reading

Related articles

More in JavaScript & Frameworks
Accessibility

10 Best Accessibility Courses and Learning Resources

Learn practical web accessibility through standards, tutorials, videos, and real interface examples. These ten trusted resources help beginners and working teams build skills and choose a useful next step.

Includes: Contrast checkerNoor HaddadNoor Haddad
Accessibility

10 Best Accessibility Browser Extensions for Testing

Compare ten browser add-ons for spotting accessibility issues while you browse or build, from automated page scans to heading and contrast checks, plus advice for turning findings into fixes.

Includes: Contrast checkerTheo OkaforTheo Okafor
Accessibility

12 Best Accessibility Testing Tools for Websites

Compare twelve practical accessibility testing tools, from browser extensions and automated checks to screen readers and code libraries, and learn how to choose a sensible mix for your site.

Includes: Contrast checkerNoor HaddadNoor Haddad
Typography & Colour

10 Best Color Contrast Checkers for Accessibility

Find a contrast checker for colour choices in websites and apps, from quick foreground-background tests to browser and desktop audits, with practical advice for testing real interface states.

Includes: Contrast checkerNoor HaddadNoor Haddad
AI for Design & UX

AI Heatmap Analysis and Usability Test Recordings

Use AI to organise usability recordings and heatmap evidence without inventing motives. Build a reviewable workflow for consent, transcripts, themes and product decisions.

Includes: Contrast checkerMaya LindqvistMaya Lindqvist
UI Design

Tabs vs Segmented Control: When to Use Each

Separate content navigation from value selection, then design tabs and segmented controls with clear labels, selected states, responsive layouts, and keyboard behavior.

Includes: Contrast checkerTheo OkaforTheo Okafor