Full Stack / 6 min read
Mastering Error Boundaries in React: Build Resilient Apps Without Crashes
A practical guide to handling UI failures gracefully, logging errors effectively, and protecting user experience in modern React…
Mastering Error Boundaries in React: Build Resilient Apps Without Crashes
A practical guide to handling UI failures gracefully, logging errors effectively, and protecting user experience in modern React applications.

No matter how carefully you write your code, errors happen. A third-party chart library fails. An API returns unexpected data. A component throws during rendering.
Without protection, even a single issue can bring down your entire React app.
That’s where error boundaries come in. They help you contain failures, show fallback UI, and prevent a complete application crash. Let’s break down how they work, where they fall short, and how to use them effectively in real-world projects.
What Are Error Boundaries in React?
Error boundaries are special React components that catch JavaScript errors occurring in their child component tree during:
- Rendering
- Lifecycle methods
- Constructors
Instead of letting the whole app crash, they display a fallback UI and optionally log the error.
They work using two lifecycle methods:
static getDerivedStateFromError(error)
Updates state to trigger a fallback UI.componentDidCatch(error, info)
Lets you log the error and access the component stack trace.
However, they only catch errors during the synchronous render phase — not everything.
What They Catch (and What They Don’t)
Here’s a simplified breakdown:
✅ Errors They Catch
- JSX rendering errors
- Errors in lifecycle methods (e.g.,
componentDidMount,useEffect) - Constructor errors
❌ Errors They Don’t Catch
- Event handler errors (
onClick,onSubmit) - Async errors (
setTimeout,setInterval) - Promise rejections (
fetch,async/await) - Server-side rendering (SSR) errors
- Errors inside the boundary itself
Why? Because async and event handler code runs outside React’s render cycle. Error boundaries are not a universal try/catch for components.
Understanding this limitation is critical when designing a robust error strategy.
Implementation: Class Components vs Libraries
Only class components can natively act as error boundaries.
But most modern apps rely heavily on functional components. That’s where the popular npm package react-error-boundary becomes useful.
It allows you to use error boundaries in functional components with a clean API:
<ErrorBoundary
fallbackRender={({ error, resetErrorBoundary }) => (
<div>
Failed: {error.message}
<button onClick={resetErrorBoundary}>Retry</button>
</div>
)}
onError={(error, componentStack) => logError(error, componentStack)}
>
<RiskyComponent />
</ErrorBoundary>Key features include:
fallback,FallbackComponent, orfallbackRenderfor custom UIonErrorfor loggingonResetandresetKeysfor recoveryuseErrorBoundary()hook for triggering boundaries from async code
This makes it much easier to integrate boundaries into modern React projects.
Handling Async and Event Handler Errors
Since error boundaries don’t catch async errors automatically, you must manually bring them back into the render cycle.
Here’s a common pattern:
const [error, setError] = useState(null);
useEffect(() => {
fetchData().catch(err => setError(err));
}, []);
if (error) throw error;By re-throwing the error during render, the boundary can catch it.
If you’re using react-error-boundary, the useErrorBoundary() hook simplifies this:
const { showBoundary } = useErrorBoundary();
useEffect(() => {
fetch('/api/data').catch(showBoundary);
}, []);For global promise rejections, you can also listen to:
window.addEventListener('unhandledrejection', ...)This layered approach gives better coverage.
Where Should You Place Error Boundaries?
Strategic placement matters more than wrapping everything.
🎯 Good Places
- Dashboard widgets
- Third-party SDK integrations
- Rich text editors
- API-driven charts
- Route-level components
❌ Avoid
- Wrapping the entire app
- Overusing boundaries everywhere
Why not wrap the whole app? Because it hides serious issues and makes debugging harder.
A better approach is granular isolation — if one widget fails, the rest of the page should still work.
Designing a Good Fallback UI
A fallback UI should:
- Use clear, non-technical language
- Offer a retry option
- Avoid exposing stack traces in production
For example:
“We hit a snag. This section couldn’t load.”
[Try Again]
This keeps users informed without overwhelming them.
Logging and Monitoring in Production
Catching an error is only half the job. You also need visibility.
Inside componentDidCatch, log:
- The error message
- The component stack
- Timestamp
- User context (if available)
Many teams integrate with monitoring tools like Sentry for production-grade tracking.
Sentry provides its own ErrorBoundary wrapper and automatically captures:
- Error stack traces
- Component hierarchy
- Session breadcrumbs (if enabled)
For async cases, you can manually report using:
Sentry.captureException(error)This ensures you don’t miss important production issues.
Framework-Specific Support
Modern frameworks simplify error handling.
In Next.js
You can create an error.js file inside a route segment. It automatically acts as an error boundary for that segment and provides a reset() function to retry rendering.
This reduces boilerplate and improves developer experience.
In React Router
Route-level boundaries can catch navigation and loader errors. You can access error data using useRouteError().
These built-in abstractions make error isolation easier.
Performance Considerations
When used thoughtfully, error boundaries have minimal performance impact.
But be careful:
- Too many boundaries can trigger unnecessary re-renders
- Deep nesting complicates debugging
- Poor placement may hide serious failures
Best practice:
- Wrap independent, high-risk components
- Avoid wrapping every component
- Profile performance when scaling large apps
Well-placed boundaries improve user experience without hurting speed.
Limitations and Current Gaps
Some limitations remain:
- No native SSR error boundary pattern in vanilla React
- Limited benchmarks comparing third-party solutions
- Sparse documentation for advanced SSR strategies like streaming or partial hydration
Until React introduces deeper SSR support, framework-specific solutions (like Next.js error.js) are the most reliable option.
Key Takeaways
- Error boundaries prevent full React app crashes by catching render-phase errors.
- They work only during synchronous rendering — not for event handlers or async code.
- Functional components require libraries like
react-error-boundary. - Async errors must be manually bridged back into the render cycle.
- Place boundaries strategically around high-risk components.
- Always integrate logging (e.g., Sentry) for production visibility.
- Frameworks like Next.js simplify route-level error handling.
Building resilient React applications isn’t about eliminating every error — it’s about handling failures gracefully.
At Dev Simplified, We Value Your Feedback 📊
👉 Follow us not to miss any updates.
👉 Have any suggestions? Let us know in the comments!