Sitemap

Error Boundaries: Handling Errors in React

8 min readApr 27, 2026

--

Press enter or click to view image in full size

When Errors Crash Your App

You build a React app. Component throws error. Entire app breaks. White screen. No UI. Users see nothing. Broken. You check console. TypeError. But users don’t check consoles. They leave.

You add try-catch. Doesn’t catch render errors. React unmounts everything. Still white screen. You can’t catch errors in render with try-catch. JavaScript limitation.

You want graceful degradation. Error in sidebar shouldn’t crash whole page. Error in widget shouldn’t break dashboard. Isolate failures. Show fallback UI. Log error. Keep rest of app working.

Error boundaries solve this. React component that catches JavaScript errors anywhere in child component tree. Logs errors. Displays fallback UI. Prevents entire app crash. Essential for production apps.

QUICK SUMMARY

What You’ll Learn: Error Boundaries are React components that catch JavaScript errors in child components. Must be class components (no hooks yet). Implement componentDidCatch to log errors and getDerivedStateFromError to render fallback UI. Catches errors in render, lifecycle methods, and constructors. Does NOT catch errors in event handlers, async code, server-side rendering, or errors in the boundary itself. Wrap components with error boundary to isolate failures. Multiple boundaries for granular control. Report errors to logging services like Sentry. Use try-catch for event handlers and async code. Development shows error overlay, production shows fallback. Reset error boundary with key prop. Combine with Suspense for loading states.

Why It Matters:

  • Prevent white screen of death
  • Graceful error handling
  • Better user experience
  • Isolate component failures
  • Log errors for debugging
  • Production-ready apps
  • Professional error handling

Key Concepts:

  • Error Boundary component
  • componentDidCatch lifecycle
  • getDerivedStateFromError static method
  • Fallback UI rendering
  • Error logging and reporting
  • Component tree isolation
  • Error boundary placement
  • Event handler try-catch

What You’ll Build:

  • Error Boundary component
  • Custom fallback UI
  • Error logging integration
  • Multiple error boundaries
  • Error reset functionality
  • Complete error handling system
  • Production-ready setup

Time Investment: Basic error boundaries take 30–60 minutes to implement. Complete error handling takes practice.

Prerequisites:

  • React fundamentals (Post 11)
  • Class components (Post 10)
  • Component lifecycle
  • Try-catch basics

FULL DETAILED ARTICLE

The Problem

Component error crashes app:

// Buggy component
function BuggyComponent() {
const [count, setCount] = useState(0);

if (count === 5) {
throw new Error('I crashed!'); // App breaks!
}

return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
function App() {
return (
<div>
<h1>My App</h1>
<BuggyComponent /> {/* Error here breaks entire app */}
<OtherComponent /> {/* Never renders if BuggyComponent crashes */}
</div>
);
}
// Result: White screen of death
// User sees nothing
// App completely broken

Basic Error Boundary

Create error boundary component:

import React from 'react';
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}

static getDerivedStateFromError(error) {
// Update state so next render shows fallback UI
return { hasError: true };
}

componentDidCatch(error, errorInfo) {
// Log error to error reporting service
console.error('Error caught by boundary:', error, errorInfo);
}

render() {
if (this.state.hasError) {
// Render fallback UI
return <h1>Something went wrong.</h1>;
}

return this.props.children;
}
}
// Usage
function App() {
return (
<ErrorBoundary>
<BuggyComponent /> {/* Error caught by boundary */}
</ErrorBoundary>
);
}
// Now: Error caught, fallback UI shown
// Rest of app still works if outside boundary

Custom Fallback UI

Better error display:

class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = {
hasError: false,
error: null,
errorInfo: null
};
}

static getDerivedStateFromError(error) {
return { hasError: true };
}

componentDidCatch(error, errorInfo) {
this.setState({
error: error,
errorInfo: errorInfo
});

// Log to error service
console.error('Error:', error);
console.error('Error Info:', errorInfo);
}

render() {
if (this.state.hasError) {
return (
<div style={{
padding: '20px',
background: '#ffebee',
border: '1px solid #f44336',
borderRadius: '4px',
margin: '20px'
}}>
<h2 style={{ color: '#d32f2f', margin: '0 0 10px 0' }}>
Oops! Something went wrong
</h2>
<p style={{ margin: '0 0 10px 0' }}>
We're sorry for the inconvenience. The error has been reported.
</p>

{process.env.NODE_ENV === 'development' && (
<details style={{ marginTop: '10px' }}>
<summary style={{ cursor: 'pointer', fontWeight: 'bold' }}>
Error details
</summary>
<pre style={{
marginTop: '10px',
padding: '10px',
background: 'white',
borderRadius: '4px',
overflow: 'auto'
}}>
{this.state.error && this.state.error.toString()}
{this.state.errorInfo && this.state.errorInfo.componentStack}
</pre>
</details>
)}

<button
onClick={() => window.location.reload()}
style={{
marginTop: '10px',
padding: '10px 20px',
background: '#d32f2f',
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: 'pointer'
}}
>
Reload Page
</button>
</div>
);
}

return this.props.children;
}
}

Error Reporting

Integrate error logging:

// Install Sentry
// npm install @sentry/react
import * as Sentry from '@sentry/react';// Initialize in index.js
Sentry.init({
dsn: 'your-sentry-dsn',
environment: process.env.NODE_ENV,
tracesSampleRate: 1.0
});
// Error Boundary with Sentry
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}

static getDerivedStateFromError(error) {
return { hasError: true };
}

componentDidCatch(error, errorInfo) {
// Report to Sentry
Sentry.captureException(error, {
contexts: {
react: {
componentStack: errorInfo.componentStack
}
}
});
}

render() {
if (this.state.hasError) {
return <h1>Something went wrong.</h1>;
}

return this.props.children;
}
}
// Or use Sentry's built-in error boundary
import { ErrorBoundary } from '@sentry/react';
function App() {
return (
<ErrorBoundary fallback={<ErrorFallback />} showDialog>
<YourApp />
</ErrorBoundary>
);
}

Multiple Error Boundaries

Get Yakhilesh’s stories in your inbox

Join Medium for free to get updates from this writer.

Isolate different parts:

function App() {
return (
<div>
<Header /> {/* No error boundary - should never crash */}

<ErrorBoundary name="Sidebar">
<Sidebar /> {/* If crashes, only sidebar shows fallback */}
</ErrorBoundary>

<main>
<ErrorBoundary name="Main Content">
<MainContent /> {/* If crashes, only main shows fallback */}
</ErrorBoundary>
</main>

<ErrorBoundary name="Footer">
<Footer /> {/* If crashes, only footer shows fallback */}
</ErrorBoundary>
</div>
);
}
// Enhanced boundary with name prop
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}

static getDerivedStateFromError(error) {
return { hasError: true };
}

componentDidCatch(error, errorInfo) {
console.error(`Error in ${this.props.name}:`, error);
}

render() {
if (this.state.hasError) {
return (
<div className="error-fallback">
<p>Error in {this.props.name}</p>
<button onClick={() => this.setState({ hasError: false })}>
Try Again
</button>
</div>
);
}

return this.props.children;
}
}

Reset Error Boundary

Allow recovery without reload:

class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}

static getDerivedStateFromError(error) {
return { hasError: true };
}

componentDidCatch(error, errorInfo) {
console.error('Error:', error, errorInfo);
}

resetError = () => {
this.setState({ hasError: false });
};

render() {
if (this.state.hasError) {
return (
<div>
<h2>Something went wrong</h2>
<button onClick={this.resetError}>Try Again</button>
</div>
);
}

return this.props.children;
}
}
// Reset with key prop (remounts component)
function Parent() {
const [key, setKey] = useState(0);

return (
<ErrorBoundary key={key}>
<BuggyComponent />
<button onClick={() => setKey(k => k + 1)}>
Reset
</button>
</ErrorBoundary>
);
}

What Error Boundaries Don’t Catch

Important limitations:

class MyComponent extends React.Component {
handleClick = () => {
try {
// Error Boundary DOES NOT catch this
throw new Error('Event handler error');
} catch (error) {
// Must use try-catch for event handlers
console.error('Caught in event handler:', error);
}
};

async componentDidMount() {
try {
// Error Boundary DOES NOT catch async errors
await fetch('/api/data');
} catch (error) {
// Must use try-catch for async code
console.error('Caught in async:', error);
}
}

render() {
// Error Boundary DOES catch this
if (this.props.shouldCrash) {
throw new Error('Render error');
}

return <button onClick={this.handleClick}>Click</button>;
}
}
// Error Boundaries catch:
// - Render errors
// - Lifecycle method errors
// - Constructor errors in child components
// Error Boundaries DON'T catch:
// - Event handler errors (use try-catch)
// - Async code errors (use try-catch)
// - Server-side rendering errors
// - Errors in the error boundary itself

Event Handler Error Handling

Handle event errors:

function MyComponent() {
const [error, setError] = useState(null);

const handleClick = () => {
try {
// Risky operation
doSomethingRisky();
} catch (error) {
setError(error);
console.error('Error:', error);
}
};

if (error) {
return <div>Error: {error.message}</div>;
}

return <button onClick={handleClick}>Click</button>;
}

Async Error Handling

Handle async errors:

function DataFetcher() {
const [data, setData] = useState(null);
const [error, setError] = useState(null);
const [loading, setLoading] = useState(true);

useEffect(() => {
const fetchData = async () => {
try {
setLoading(true);
setError(null);

const response = await fetch('/api/data');

if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}

const data = await response.json();
setData(data);
} catch (error) {
setError(error);
console.error('Fetch error:', error);
} finally {
setLoading(false);
}
};

fetchData();
}, []);

if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
if (!data) return <div>No data</div>;

return <div>{JSON.stringify(data)}</div>;
}

Reusable Error Handler Hook

Custom hook for error handling:

function useErrorHandler() {
const [error, setError] = useState(null);

const handleError = useCallback((error) => {
setError(error);
console.error('Error:', error);

// Report to error service
if (process.env.NODE_ENV === 'production') {
Sentry.captureException(error);
}
}, []);

const clearError = useCallback(() => {
setError(null);
}, []);

return { error, handleError, clearError };
}
// Usage
function MyComponent() {
const { error, handleError, clearError } = useErrorHandler();

const handleClick = async () => {
try {
await doSomething();
} catch (err) {
handleError(err);
}
};

if (error) {
return (
<div>
<p>Error: {error.message}</p>
<button onClick={clearError}>Dismiss</button>
</div>
);
}

return <button onClick={handleClick}>Click</button>;
}

Production vs Development

Different error displays:

class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null };
}

static getDerivedStateFromError(error) {
return { hasError: true, error };
}

componentDidCatch(error, errorInfo) {
// Always log
console.error('Error:', error, errorInfo);

// Report in production
if (process.env.NODE_ENV === 'production') {
Sentry.captureException(error);
}
}

render() {
if (this.state.hasError) {
// Development: show details
if (process.env.NODE_ENV === 'development') {
return (
<div style={{ padding: '20px', background: '#ffebee' }}>
<h2>Development Error</h2>
<pre>{this.state.error.toString()}</pre>
</div>
);
}

// Production: friendly message
return (
<div style={{ padding: '20px', textAlign: 'center' }}>
<h2>Oops! Something went wrong</h2>
<p>We're working on fixing this issue.</p>
<button onClick={() => window.location.reload()}>
Reload Page
</button>
</div>
);
}

return this.props.children;
}
}

Complete Error Handling System

Full implementation:

// ErrorBoundary.jsx
import React from 'react';
import * as Sentry from '@sentry/react';
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = {
hasError: false,
error: null,
errorInfo: null
};
}

static getDerivedStateFromError(error) {
return { hasError: true };
}

componentDidCatch(error, errorInfo) {
this.setState({
error,
errorInfo
});

// Log to console
console.error('Error Boundary caught error:', {
error,
errorInfo,
componentName: this.props.name || 'Unknown'
});

// Report to Sentry
Sentry.captureException(error, {
contexts: {
react: {
componentStack: errorInfo.componentStack
}
},
tags: {
errorBoundary: this.props.name || 'default'
}
});

// Call optional onError callback
if (this.props.onError) {
this.props.onError(error, errorInfo);
}
}

handleReset = () => {
this.setState({
hasError: false,
error: null,
errorInfo: null
});

if (this.props.onReset) {
this.props.onReset();
}
};

render() {
if (this.state.hasError) {
// Use custom fallback if provided
if (this.props.fallback) {
if (typeof this.props.fallback === 'function') {
return this.props.fallback({
error: this.state.error,
reset: this.handleReset
});
}
return this.props.fallback;
}

// Default fallback UI
return (
<div className="error-boundary-fallback">
<h2>Something went wrong</h2>
<p>
{this.props.name
? `Error in ${this.props.name}`
: 'An error occurred'}
</p>
<button onClick={this.handleReset}>
Try Again
</button>
</div>
);
}

return this.props.children;
}
}
export default ErrorBoundary;// Usage
function App() {
return (
<ErrorBoundary
name="App"
fallback={({ error, reset }) => (
<div>
<h1>App Error</h1>
<p>{error.message}</p>
<button onClick={reset}>Reset</button>
</div>
)}
onError={(error, info) => {
console.log('App error:', error);
}}
>
<YourApp />
</ErrorBoundary>
);
}

Summary: Your Error Handling Checklist

Error Boundaries:

  • Class component (no hooks)
  • getDerivedStateFromError for state
  • componentDidCatch for logging
  • Wrap components to isolate errors
  • Custom fallback UI

What They Catch:

  • Render errors ✓
  • Lifecycle errors ✓
  • Constructor errors ✓
  • Event handlers ✗ (use try-catch)
  • Async code ✗ (use try-catch)

Best Practices:

  • Multiple boundaries for isolation
  • Log errors to service (Sentry)
  • Different UI for dev/prod
  • Allow error reset
  • Provide helpful messages

Event/Async Errors:

  • Use try-catch in event handlers
  • Use try-catch in async functions
  • Show error state in component
  • Clear error state on retry

Practice Challenges

Implement error handling:

Challenge 1: Global Error Boundary Create app-level error boundary.

Challenge 2: Component-Level Boundaries Add boundaries to major sections.

Challenge 3: Error Reporting Integrate Sentry or similar service.

Challenge 4: Async Error Handling Handle all async errors gracefully.

Challenge 5: Error Recovery Implement reset and retry logic.

Related Posts:

  • Post 10: Class Components
  • Post 11: React Fundamentals
  • Post 14: useEffect Hook

You are at post 66 of the 75-post series. Error handling is now in your toolkit.

--

--

Yakhilesh
Yakhilesh

Written by Yakhilesh

I write about work, learning, and the things we overcomplicate. Clear thinking, honest notes from real experience. No hype. akhilesh.pages.dev