Web

Global React Error Boundary: Centralized Config Local Fallback

Learn how to configure a global React Error Boundary with centralized settings for fallback UI, logging, and reset behavior, but local rendering only for errored subtrees. Keep the rest of the app functional using Context and react-error-boundary.

1 answer 1 view

How to configure a global React Error Boundary with centralized settings (fallback UI, logging, reset behavior) but local fallback rendering only for the errored subtree in React JS?

Currently, a single global Error Boundary around the entire app causes the whole UI to show the fallback on any error, which is disruptive. I want global configuration in one place, but local behavior where only the broken section, route, or widget displays the fallback, keeping the rest of the app functional.

Key requirements:

  • Centralized config for fallback, logging, etc.
  • Errors caught by nearest boundary, discarding only the subtree inside it.
  • React JS (not TypeScript).

react error boundary and error boundary react can be configured centrally while still letting each ErrorBoundary render only its own fallback UI. Put shared settings (fallback component, onError logger, reset strategy) into an ErrorConfig Context and expose a small AppErrorBoundary wrapper (using the react-error-boundary library or a tiny class-based wrapper) that reads that config and you use locally around routes/widgets — not around the entire app. That way the nearest boundary catches the error and only that subtree shows the fallback, keeping the rest of the UI intact.


Contents


Centralized react error boundary configuration

Why does a single global ErrorBoundary break your whole UI? Because whatever boundary catches an error replaces its entire subtree with the fallback UI. If that boundary wraps your entire app, any component error will take down everything. The official React docs on Error Boundaries explain this behavior and why local boundaries are usually better.

Goal: keep configuration in one place (fallback UI, logging, reset policy) but mount many small boundaries so the nearest one handles errors for only its subtree. A central config holds shared behavior; local wrappers read that config and provide the actual isolation. This pattern also maps to advice from community writeups about mixing central configuration with local boundaries — see the practical examples on refine.dev and the Context-based approach in the PriorCoder article.

Search behavior note: some searches use terms like “react ошибка” or “react обработка ошибок” — same problem/solutions apply across languages.


Local fallback rendering with error boundary react

Conceptually: React walks up the component tree and the first ErrorBoundary it finds for that subtree handles the error. So you want to wrap only the parts that can reasonably fail:

  • Third-party widgets
  • Route-based pages (per-route boundaries)
  • Heavy components that fetch lots of data
  • Small UI blocks that should degrade independently (dashlets, side panels)

Wrap those parts with a small AppErrorBoundary that reads the global config. For example:

  • Don’t: one ErrorBoundary at .
  • Do: , , etc.

This preserves user state elsewhere. Want to retry a widget? Give the fallback a Retry button that calls resetErrorBoundary (provided by the fallback props), or use resetKeys so the boundary resets when data changes.


Implementation — Context + react-error-boundary (recommended)

The simplest maintainable solution is:

  1. Put configuration in a context (fallback component, onError logger, onReset, default resetKeys).
  2. Build a tiny AppErrorBoundary wrapper that pulls that config and mounts a react-error-boundary ErrorBoundary.
  3. Use the wrapper locally around widgets/routes.

The react-error-boundary library handles resetErrorBoundary and resetKeys cleanly; see the project for details: https://github.com/bvaughn/react-error-boundary

Example (plain JavaScript / React):

  1. ErrorConfigContext.js
jsx
import React, { createContext, useContext } from "react";

/* Minimal default fallback component (keeps UI tiny & safe) */
function DefaultFallback({ error, resetErrorBoundary }) {
 return (
 <div role="alert" style={{ padding: 12, border: "1px solid #e66" }}>
 <strong>Something went wrong.</strong>
 <div style={{ whiteSpace: "pre-wrap", marginTop: 8 }}>
 {String(error?.message)}
 </div>
 <button onClick={resetErrorBoundary} style={{ marginTop: 8 }}>
 Retry
 </button>
 </div>
 );
}

const defaultConfig = {
 fallbackComponent: DefaultFallback,
 onError: (error, info) => { console.error("Unhandled error:", error, info); },
 onReset: () => {},
 resetKeys: undefined, // default: no automatic reset on data change
};

const ErrorConfigContext = createContext(defaultConfig);

export function ErrorProvider({ config = {}, children }) {
 const merged = { ...defaultConfig, ...config };
 return (
 <ErrorConfigContext.Provider value={merged}>
 {children}
 </ErrorConfigContext.Provider>
 );
}

export function useErrorConfig() {
 return useContext(ErrorConfigContext);
}
  1. AppErrorBoundary.js (wrapper around react-error-boundary)
jsx
import React from "react";
import { ErrorBoundary } from "react-error-boundary";
import { useErrorConfig } from "./ErrorConfigContext";

export function AppErrorBoundary({ children, fallbackComponent, resetKeys }) {
 const config = useErrorConfig();
 const Fallback = fallbackComponent || config.fallbackComponent;

 return (
 <ErrorBoundary
 FallbackComponent={Fallback}
 onError={(error, info) => {
 try {
 config.onError?.(error, info);
 } catch (e) {
 // logging shouldn't throw the app into an even worse state
 console.error("Error while logging:", e);
 }
 }}
 onReset={() => {
 try {
 config.onReset?.();
 } catch (e) { /* swallow */ }
 }}
 resetKeys={resetKeys ?? config.resetKeys}
 >
 {children}
 </ErrorBoundary>
 );
}
  1. Global config & usage (App.js)
jsx
import React from "react";
import * as Sentry from "@sentry/browser"; // example, optional
import { ErrorProvider } from "./ErrorConfigContext";
import { AppErrorBoundary } from "./AppErrorBoundary";
import Dashboard from "./Dashboard";
import Sidebar from "./Sidebar";

/* Initialize Sentry elsewhere, if you're using it */
const appErrorConfig = {
 fallbackComponent: null, // you can pass a custom component here (see below)
 onError: (error, info) => {
 // Send to Sentry / whatever
 Sentry.captureException(error, { extra: { componentStack: info?.componentStack } });
 },
 onReset: () => {
 // Optional global reset hook
 },
 resetKeys: undefined
};

function App() {
 return (
 <ErrorProvider config={appErrorConfig}>
 <main>
 <AppErrorBoundary>
 <Header />
 </AppErrorBoundary>

 <AppErrorBoundary resetKeys={[/* e.g. user.id */]}>
 <Dashboard />
 </AppErrorBoundary>

 <AppErrorBoundary>
 <Sidebar />
 </AppErrorBoundary>
 </main>
 </ErrorProvider>
 );
}

Fallback contract: the component you provide as fallbackComponent must accept the props the library passes, e.g. ({ error, resetErrorBoundary }) or ({ error, resetErrorBoundary, componentStack }) if you want to show stack info (avoid showing stack to end users).

Reset strategies:

  • resetKeys: supply an array of values; when any value changes, boundary resets automatically.
  • resetErrorBoundary: the fallback receives a resetErrorBoundary callback to programmatically clear the error (e.g., after a retry).
  • Key remount: wrap boundary in a parent that changes key to force remount when you want a full reset.

This approach centralizes behavior (logging, fallback component shape) but keeps actual boundaries local, so only the erroring subtree is replaced.


Implementation — Custom class ErrorBoundary + Context (legacy)

If you need or prefer a class-based boundary (React error boundaries must be classes for getDerivedStateFromError / componentDidCatch), you can build a tiny wrapper that reads the same ErrorConfigContext.

Example:

jsx
import React from "react";
import { ErrorConfigContext } from "./ErrorConfigContext";

export class ClassErrorBoundary extends React.Component {
 static contextType = ErrorConfigContext;

 constructor(props) {
 super(props);
 this.state = { error: null, errorInfo: null };
 }

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

 componentDidCatch(error, errorInfo) {
 try {
 this.context.onError?.(error, errorInfo);
 } catch (e) { /* ignore logging errors */ }
 this.setState({ errorInfo });
 }

 componentDidUpdate(prevProps) {
 // resetKey is a simple way to remount/reset the boundary
 if (this.props.resetKey !== prevProps.resetKey && this.state.error) {
 this.setState({ error: null, errorInfo: null });
 this.context.onReset?.();
 }
 }

 render() {
 if (this.state.error) {
 const Fallback = this.props.fallbackComponent || this.context.fallbackComponent;
 return (
 <Fallback
 error={this.state.error}
 reset={() => {
 this.setState({ error: null, errorInfo: null });
 this.context.onReset?.();
 }}
 />
 );
 }
 return this.props.children;
 }
}

Notes:

  • Custom class approach works without an external dependency but requires you to implement reset behavior (resetKey or manual reset).
  • The react-error-boundary library is smaller and already battle-tested for reset semantics; if you can, prefer it.

Logging and reset behavior

What does your central config typically expose?

  • fallbackComponent: the UI component used by local boundaries (keeps consistent UX).
  • onError(error, info): centralized logging hook (Sentry, Datadog, console, custom).
  • onReset(): optional global callback when any boundary resets.
  • resetKeys: default resetKeys array (optional).

Logging tips

  • Send error plus component stack (info.componentStack) to your error tracker. Example with Sentry:
    Sentry.captureException(error, { extra: { componentStack: info?.componentStack } });
  • Don’t send PII. Scrub payloads and avoid full request bodies.
  • Keep logging non-blocking and guarded with try/catch (so logging failures don’t crash the app).

Reset strategies — pick one:

  • resetKeys: best when the error is tied to a piece of data (e.g., id, version). Pass [itemId] so changes reset automatically.
  • resetErrorBoundary: best for a Retry button inside the fallback UI.
  • key-remount: parent changes key to force remount when you need a hard reset.

Catching async & non-render errors

  • Error boundaries only catch render/lifecycle errors, not event handlers or unhandled promise rejections.
  • For async code, either:
  • catch and set local error state and then throw that error inside render (so boundary catches it), or
  • call your central logger and show a controlled fallback UI.
    Example pattern for data fetching:
jsx
useEffect(() => {
 fetch(url)
 .then(res => res.json())
 .then(setData)
 .catch(err => setFetchError(err)); // below, if fetchError exists, `if (fetchError) throw fetchError;`
}, [url]);

if (fetchError) throw fetchError;
  • For uncaught rejections / global errors, also wire window.addEventListener('unhandledrejection', ...) and window.addEventListener('error', ...) to feed the same onError for visibility (but keep UI handling local).

Patterns: where to place boundaries

Recommended placements (practical examples):

  • Per route: wrap each route element — keeps a broken page from taking the whole app.
  • Per widget or card: any component that loads remote scripts or third-party UI.
  • Around forms that contain complex validation logic — prevents losing the rest of the page on a bug.
  • Around heavy UIs like dashboards or charts.
  • Optional: a thin top-level boundary to catch anything unforeseen (it should be the last resort and show a tiny “critical app error” fallback).

Example (React Router v6):

jsx
<Routes>
 <Route path="/" element={<Home />} />
 <Route
 path="/dashboard"
 element={
 <AppErrorBoundary fallbackComponent={DashboardFallback}>
 <Dashboard />
 </AppErrorBoundary>
 }
 />
</Routes>

The rule of thumb: wrap things that have independent state and you can afford to remount or show a small fallback for.


Common pitfalls and FAQ

Q: I wrapped the whole app and still want central logging — why is the whole UI replaced?
A: Because the outer boundary is the nearest boundary for any thrown error. The fix: keep logging centralized (context) but move boundaries inside to isolate subtrees.

Q: How do I retry after an error?
A: Provide a Retry button in the fallback that calls resetErrorBoundary (react-error-boundary) or triggers a parent key change / resetKey update.

Q: My fallback itself throws an error — now what?
A: Keep fallbacks tiny and defensive. A fallback should avoid heavy logic or third-party calls. Test fallbacks explicitly.

Q: Are event handler errors caught?
A: No. Event handlers run outside render; wrap handler bodies in try/catch and forward to central logger, or set error state and throw from render.

Q: Can I surface non-rendering errors to a boundary?
A: Yes — set an error state and throw it within render, or call a context function that signals a boundary. The PriorCoder pattern shows a Context-based central state approach.


Sources


Conclusion

Centralize configuration (fallback UI, logging, reset policy) in an ErrorConfig Context and expose a thin AppErrorBoundary wrapper that reads that config. Then put that wrapper locally around routes, widgets, and other error-prone subtrees so the nearest react error boundary handles errors and only the broken subtree shows the fallback. Use react-error-boundary for reliable reset semantics, send errors to a central onError logger, and keep fallback UIs small and robust — that combination gives you global settings with local fallback rendering using a react error boundary.

Authors
Verified by moderation
Moderation
Global React Error Boundary: Centralized Config Local Fallback