Error boundaries catch render-time errors in child components, but they do not catch every error a React app can throw. Knowing the gaps tells you where to add try/catch, promise handling, or server-side checks instead of relying on a boundary.
What boundaries actually cover
A boundary catches an error thrown while React renders a child component. It swaps in a fallback and keeps the rest of the tree alive, so one broken widget does not take down the page. The mechanism is the same class you build once in React error boundaries explained with a reusable example.
The four cases they miss
| Error source | Why a boundary misses it | What to use instead |
|---|---|---|
| Event handlers | handlers run outside render | try/catch in the handler |
| Async code | timers and promises resolve later | a catch and error state |
| Server rendering | no boundary mounts on the server | try/catch in server code |
| The boundary itself | it cannot catch its own error | a parent boundary or top-level handler |
Event handlers, timers, and promises all run after render finishes, so no boundary is watching. Server rendering happens before any boundary mounts in the browser. A boundary that throws cannot catch itself.
Event handler errors
A click handler that throws does not trigger the nearest boundary. Catch it at the call site and turn it into visible UI.
import { useState } from "react";
export default function SaveButton({ onSave }) {
const [error, setError] = useState("");
async function handleClick() {
setError("");
try {
await onSave();
} catch {
setError("Saving failed. Try again.");
}
}
return (
<>
<button onClick={handleClick}>Save</button>
{error && <p role="alert">{error}</p>}
</>
);
}When onSave rejects, the catch sets a message and the paragraph appears. The boundary above never sees the error because the rejection happened in an async handler, not during render.
Async errors
A rejected fetch or a throwing timer callback also misses boundaries. Attach a catch and store the failure in state, then render an error state.
Keep the loading and error branches separate so the user sees which one happened. This is the pattern from how to handle loading, error, empty, and success states.
The exception: transitions
Errors thrown inside the function you pass to startTransition are caught by error boundaries. Marking an update as a transition lets React retry it, and a boundary can catch the error from that retried render. This is the one async-shaped case where a boundary still applies.
What to learn next
For the render errors a boundary does catch, isolate the throwing component before you guard it. Naming the phase first keeps that isolation quick.
Rune AI
Key Insights
- Boundaries catch only render-time errors in children.
- Event handlers need try/catch at the call site.
- Async code needs a catch and an error state.
- Server rendering needs try/catch in server code.
- Errors inside startTransition are still caught.
Frequently Asked Questions
Does an error boundary catch a rejected promise?
What catches event handler errors?
Conclusion
Error boundaries catch only render-time errors in children. Event handlers, async work, server rendering, and the boundary's own errors stay outside, so each needs its own handling.
More in this topic
How to Build a Dropdown Menu in React
Build a React dropdown menu with the ARIA menu button pattern. Handle open and close, keyboard arrows, and clicks outside the menu.
How to Animate React Components with Motion
Animate React components with the Motion library. Set up motion, add enter, hover, and exit animations, and respect reduced motion.
Headless UI Components Explained: Logic Without Locked Styling
Understand headless UI components and how libraries like Radix give you unstyled, accessible behavior that you style yourself.