What this usually means
React error boundaries only catch errors thrown during rendering, in lifecycle methods, and in constructors of descendant components. They don’t intercept errors from async code, event handlers, setTimeout, or server responses. If your boundary isn't activating, the source of the error is likely outside the synchronous React tree, or your boundary placement is incorrect. It can also mean the error is thrown before React gets to mount the tree (e.g., in context providers) or outside of React entirely.
The first ten minutes — establish facts before touching code.
- 1Intentionally throw new Error('Test') inside a child component's render() to confirm the error boundary works at all.
- 2Wrap async functions in try/catch and manually set error state to mimic boundary behavior.
- 3Move the error boundary higher in the component tree and re-test to check for placement miss.
- 4Log calls to componentDidCatch() or getDerivedStateFromError() to verify if they're hit.
- 5Explicitly trigger errors in event handlers (e.g., onClick) and observe if fallback UI renders.
The specific files, logs, configs, and dashboards that usually own this bug.
- searchsrc/components/ErrorBoundary.jsx (your boundary implementation)
- searchThe placement of <ErrorBoundary> in src/App.jsx or relevant parent render method
- searchChild component code with async effects or event handlers (look for await, setTimeout)
- searchBrowser console for uncaught error stack traces and message contexts
- searchReact DevTools — inspect the component tree to verify where the boundary is mounted
- searchNetwork panel for async API calls returning unexpected errors
Practical causes, not theory. These are the things you will actually find.
- warningErrors thrown inside event handlers (e.g., onClick) aren’t caught by error boundaries
- warningUncaught errors from async/await or Promise chains outside of React’s render lifecycle
- warningError boundary is mounted below, not above, the failing component
- warningError thrown in a non-React callback (XHR, setTimeout, external library)
- warningCustom error boundary missing componentDidCatch or fallback UI logic
- warningBoundary not wrapped around the correct subtree (child rendered by route instead of boundary)
Concrete fix directions. Pick the one that matches your root cause.
- buildManually catch errors in event handlers and set error state to trigger fallback UI
- buildRefactor async logic to catch and surface errors inside React render flow
- buildMove <ErrorBoundary> higher in the tree so it wraps all possible points of failure
- buildImplement a global window.onerror handler for truly uncaught exceptions (as a last resort)
- buildAdd test coverage to explicitly throw in various lifecycle points and event handlers
A fix you cannot prove is a guess. Close the loop.
- verifiedTrigger known synchronous render errors and confirm fallback UI appears
- verifiedThrow errors inside async functions and verify error boundary is not triggered (proving case)
- verifiedCatch async errors manually and set a flag — confirm fallback UI displays
- verifiedCheck browser console — no uncaught error for code paths intentionally handled by the boundary
- verifiedConfirm componentDidCatch or getDerivedStateFromError invoked via console logging
Things that make this bug worse or harder to find.
- warningRelying on error boundaries to catch async or event handler errors
- warningPlacing the boundary below the at-risk component (boundary must be a parent)
- warningFailing to implement both fallback UI and componentDidCatch/getDerivedStateFromError
- warningAssuming try/catch in async code is covered by boundaries
- warningIgnoring uncaught error logs in the browser console
Async Request Failure Bypasses Error Boundary in a Production React App
Timeline
- 10:32User reports app freeze after clicking 'Save' button; no error fallback seen.
- 10:39Engineer checks logs: uncaught promise rejection for Axios call in console.
- 10:44Throws error in render(): error boundary fallback appears as expected.
- 10:50Introduces throw in async save handler; boundary does not catch the error.
- 11:02Moves error boundary up one level; no change for async error.
- 11:09Wraps async handler in try/catch, sets hasError state, fallback UI appears.
- 11:13Adds manual catch for all async flows, writes regression test for event handler error.
We got a production bug: when users clicked 'Save', the UI would freeze but never show our error fallback component. I checked our ErrorBoundary.jsx implementation and confirmed the fallback rendered for errors thrown inside render(), so I initially thought the boundary was working.
On closer inspection, the crash happened after an async Axios request failed in a Redux thunk. The error was visible in the browser console as an uncaught promise rejection, but the error boundary wasn’t activated. I moved the boundary higher in the tree, but it made no difference.
The root cause: React error boundaries can't catch errors from async code or event handlers. I fixed it by wrapping the async code in try/catch, setting an explicit hasError flag in local component state, and rendering the fallback UI manually. We added a test to check for error fallback on every button handler.
Root cause
Async exception thrown in an event handler outside of React's render lifecycle — invisible to error boundaries.
The fix
Manually catch errors in async handlers and force fallback UI via component state.
The lesson
React error boundaries are not a global rescue net. Handle async and event-driven errors explicitly.
Error boundaries only catch errors during rendering, in lifecycle methods (componentDidMount, componentDidUpdate, etc.), and constructors of React components. If an error occurs outside these phases, for example in a setTimeout, a promise, or an event handler, it escapes the React error boundary mechanism.
It's a critical misconception: boundaries are *not* a catch-all exception handler. You must understand precisely where your errors occur relative to React's update cycle.
Async functions (using async/await) and event handlers (like onClick, onChange) run outside of React’s synchronous update lifecycle. Errors thrown from these aren’t propagated to boundaries.
You must wrap such code in try/catch yourself. If you want your fallback UI, set an explicit hasError state in your component and render the fallback conditionally.
Engineers often place error boundaries too low (inside the component that may error) or too high (above Router, making fallback ambiguous). Always wrap the smallest subtree at risk, but make sure the boundary is a true ancestor of the code path throwing the error.
Automated tests should explicitly simulate both render-time and async/event-handler errors, confirming expected UI fallback in either scenario.
Errors thrown outside of React (window.onerror, setTimeout, uncaught Promise rejections) aren't handled by React’s boundaries. For a global safety net, you can add a window.onerror or window.onunhandledrejection handler, but routing everything to a single fallback UI is rarely user-friendly.
For mission-critical apps, in addition to proper boundary use, implement global monitoring (Sentry, Datadog) to capture truly uncaught exceptions.
Frequently asked questions
Do error boundaries catch errors from hooks like useEffect?
No. Errors thrown inside useEffect, useLayoutEffect, or any async hook code are not caught by error boundaries. Handle these with manual try/catch.
Can I use a single error boundary at the root of my React app?
Yes, but this can lead to poor UX—one crash can blank the entire UI. It's better to wrap logical subtrees to localize failures and fallback displays.
Why doesn't my error boundary catch errors from API failures?
API failures (via fetch, Axios, etc.) are async and not part of React’s rendering phase. Wrap your API call logic in try/catch and handle errors explicitly.
Is there any way to catch errors from event handlers in an error boundary?
No. React boundaries won't catch such errors. You must manually handle exceptions in handlers and show fallback UI by controlling component state.