Error Boundaries in React: A Simple Guide to Stop Your App From Crashing
If you’ve ever shipped a React app and suddenly seen a blank white screen, you know how scary it feels. One tiny bug in a single component…
Error Boundaries in React: A Simple Guide to Stop Your App From Crashing

Error Boundaries in React
If you’ve ever shipped a React app and suddenly seen a blank white screen, you know how scary it feels. One tiny bug in a single component can bring down your whole application.
Thankfully, React gives us a powerful tool to prevent this: Error Boundaries.
In this post, you’ll learn what they are, why they matter, and how to use them — all in a simple, beginner-friendly way.
What Are Error Boundaries?
Error Boundaries are React components with a special superpower: they catch JavaScript errors anywhere in their child component tree, log those errors, and display a fallback UI instead of letting your entire application crash.
Think of them as try-catch blocks, but for React components.
What Error Boundaries Don’t Catch
They do NOT catch errors from:
- Event handlers (
onClick, etc.) - Async code (timeouts, promises, async/await)
- Server-side rendering
- Errors inside the error boundary itself
NOTE: “Why not event handlers? Because React doesn’t need Error Boundaries to recover from errors in event handlers. Unlike rendering errors, they don’t corrupt the component tree.”
The Problem They Solve
Before Error Boundaries (React 15 and earlier)
// One error in this component...
function BrokenWidget() {
const data = null;
return <div>{data.name}</div>; // TypeError: Cannot read property 'name' of null
}
// ...would crash your ENTIRE app
function App() {
return (
<div>
<Header />
<BrokenWidget /> {/* 💥 App crashes here */}
<MainContent /> {/* Never rendered */}
<Footer /> {/* Never rendered */}
</div>
);
}
Result: White screen of death. Frustrated users. Lost revenue.
After Error Boundaries (React 16+)
function App() {
return (
<div>
<Header />
<ErrorBoundary>
<BrokenWidget /> {/* 💥 Error caught */}
</ErrorBoundary>
<MainContent /> {/* Still renders! */}
<Footer /> {/* Still renders! */}
</div>
);
}
Result: Error contained. App keeps running. Users stay happy.
Creating Your First Error Boundary
Error Boundaries must be class components (for now — we’ll discuss this later). You create one by implementing either or both of these lifecycle methods:
static getDerivedStateFromError()— Renders fallback UIcomponentDidCatch()— Logs error information
Basic Implementation
import React from 'react';
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = {
hasError: false,
error: null,
errorInfo: null
};
}
// Update state so the next render shows the fallback UI
static getDerivedStateFromError(error) {
return { hasError: true };
}
// Log the error to an error reporting service
componentDidCatch(error, errorInfo) {
console.error("Error caught by Error Boundary:", error, errorInfo);
// Send to Sentry, LogRocket, or your preferred service
// logErrorToMyService(error, errorInfo);
}
render() {
if (this.state.hasError) {
// Custom fallback UI
return (
<div className="error-boundary">
<h2>🔧 Something went wrong.</h2>
<p>We're sorry for the inconvenience.</p>
<button onClick={() => this.setState({ hasError: false })}>
Try again
</button>
</div>
);
}
return this.props.children;
}
}
export default ErrorBoundary;
Breaking Down the Code
The Constructor:
this.state = {
hasError: false, // Has an error occurred?
error: null, // The error object
errorInfo: null // Component stack trace
};
getDerivedStateFromError:
- Called during the “render” phase
- Must return a value to update state
- Should be pure (no side effects)
componentDidCatch:
- Called during the “commit” phase
- Perfect for logging errors
- Can have side effects
Real-World Example
Let’s build a complete application with Error Boundaries protecting different sections.
// App.js
import React from 'react';
import ErrorBoundary from './ErrorBoundary';
import UserProfile from './UserProfile';
import CommentSection from './CommentSection';
import './App.css';
function App() {
return (
<div className="App">
<h1>🚀 My Application</h1>
{/* Each section has its own error boundary */}
<ErrorBoundary>
<UserProfile userId="123" />
</ErrorBoundary>
<hr />
<ErrorBoundary>
<CommentSection postId="456" />
</ErrorBoundary>
</div>
);
}
export default App;
// UserProfile.js - EVEN SIMPLER
import React from 'react';
function UserProfile({ userId }) {
// ✅ This throws during render - Error Boundary catches it!
if (userId === "123") {
throw new Error("Invalid user ID");
}
return (
<div className="user-profile">
<h3>👤 User Profile</h3>
<p><strong>Name:</strong> John Doe</p>
<p><strong>Email:</strong> john@example.com</p>
</div>
);
}
export default UserProfile;
What Happens?
- UserProfile throws an error → Error Boundary catches it → Shows fallback UI
- CommentSection works fine → Renders normally
- The rest of your app continues working!
Best Practices
1. Strategic Placement
Don’t wrap every single component. Place Error Boundaries at strategic levels:
function App() {
return (
<div>
{/* Route-level error boundary */}
<ErrorBoundary>
<Router>
<Routes>
{/* Page-level error boundary */}
<Route path="/dashboard" element={
<ErrorBoundary>
<Dashboard />
</ErrorBoundary>
} />
{/* Widget-level error boundary */}
<Route path="/profile" element={
<div>
<ErrorBoundary>
<UserWidget />
</ErrorBoundary>
<ErrorBoundary>
<StatsWidget />
</ErrorBoundary>
</div>
} />
</Routes>
</Router>
</ErrorBoundary>
</div>
);
}
Think of it like this:
- Top-level: Catch catastrophic failures
- Route-level: Isolate page errors
- Widget-level: Contain independent component failures
2. Meaningful Fallback UIs
// Bad:
return <div>Error</div>
// Good:
return (
<div className="error-fallback">
<img src="/error-illustration.svg" alt="Error" />
<h2>Unable to load {this.props.componentName}</h2>
<p>Please try again or contact support if the problem persists.</p>
<button onClick={this.handleRetry}>Retry</button>
<a href="/support">Contact Support</a>
</div>
);
3. Proper Error Logging
componentDidCatch(error, errorInfo) {
// Log comprehensive error information
const errorData = {
message: error.message,
stack: error.stack,
componentStack: errorInfo.componentStack,
// Context
timestamp: new Date().toISOString(),
url: window.location.href,
userAgent: navigator.userAgent,
// User info (if available)
userId: this.props.user?.id,
// App state (if using Redux)
appState: store.getState(),
};
// Send to logging service
// logToSentry(errorData);
// or
// logToCustomService(errorData);
}
4. Different Fallbacks for Different Errors
render() {
if (this.state.hasError) {
// Network error
if (this.state.error.message.includes('fetch')) {
return <NetworkErrorFallback />;
}
// Permission error
if (this.state.error.message.includes('permission')) {
return <PermissionDeniedFallback />;
}
// Default error
return <GenericErrorFallback />;
}
return this.props.children;
}
Common Pitfalls
❌ Pitfall #1: Event Handler Errors Aren’t Caught
function MyComponent() {
const handleClick = () => {
// This error WON'T be caught by Error Boundary
throw new Error('Button clicked');
};
return <button onClick={handleClick}>Click me</button>;
}
Solution: Use try-catch:
function MyComponent() {
const [error, setError] = useState(null);
const handleClick = () => {
try {
// Your code
throw new Error('Button clicked');
} catch (err) {
setError(err);
// Or report to error service
}
};
if (error) {
throw error; // Now Error Boundary can catch it
}
return <button onClick={handleClick}>Click me</button>;
}
❌ Pitfall #2: Async Errors Aren’t Caught
function MyComponent() {
useEffect(() => {
// This error WON'T be caught
setTimeout(() => {
throw new Error('Async error');
}, 1000);
}, []);
return <div>Component</div>;
}
Solution: Handle async errors explicitly:
function MyComponent() {
const [error, setError] = useState(null);
useEffect(() => {
setTimeout(() => {
try {
throw new Error('Async error');
} catch (err) {
setError(err);
}
}, 1000);
}, []);
if (error) {
throw error; // Error Boundary catches this
}
return <div>Component</div>;
}
❌ Pitfall #3: Errors in the Error Boundary Itself
// DON'T DO THIS
class BadErrorBoundary extends React.Component {
componentDidCatch(error, errorInfo) {
// This could cause an infinite loop!
this.props.onError(); // What if this throws?
}
render() {
if (this.state.hasError) {
// This could throw too!
return <ComplexComponent data={undefined} />;
}
return this.props.children;
}
}
Solution: Keep Error Boundary logic simple and safe:
class GoodErrorBoundary extends React.Component {
componentDidCatch(error, errorInfo) {
try {
// Safely handle callbacks
this.props.onError?.(error);
} catch (err) {
console.error('Error in error handler:', err);
}
}
render() {
if (this.state.hasError) {
// Simple, safe fallback
return (
<div>
<h2>Something went wrong</h2>
<button onClick={() => window.location.reload()}>
Reload
</button>
</div>
);
}
return this.props.children;
}
}
Error Boundary with React Router
import { useRouteError } from 'react-router-dom';
// Error Boundary for React Router v6.4+
function RouterErrorBoundary() {
const error = useRouteError();
return (
<div className="route-error">
<h1>😱 Route Error</h1>
<p>{error.statusText || error.message}</p>
<a href="/">Go Home</a>
</div>
);
}
// In your router configuration
const router = createBrowserRouter([
{
path: "/",
element: <Root />,
errorElement: <RouterErrorBoundary />,
children: [
{
path: "dashboard",
element: <Dashboard />,
errorElement: <RouterErrorBoundary />,
},
],
},
]);
Key Takeaways
- Error Boundaries prevent your entire app from crashing when a single component fails
- They only work for errors during rendering, lifecycle methods, and constructors — not event handlers or async code
- Must be class components (no hooks equivalent yet)
- Place them strategically at the route, page, and widget levels
- Always provide user-friendly fallback UIs with retry options
- Log errors to monitoring services like Sentry or LogRocket
- Keep the error boundary itself simple to avoid errors in error handling
Useful Links
- https://legacy.reactjs.org/docs/error-boundaries.html
- https://www.npmjs.com/package/react-error-boundary
Conclusion
Error Boundaries are one of React’s most underrated features. They transform your app from fragile to fail-safe by catching unexpected crashes before they reach your users. With just a few lines of code and smart placement, you can make your UI dramatically more stable, reliable, and professional.
Thanks for reading!
If you found this useful, please clap 👏, share this, and leave a comment below.
You can also follow me on **GitHub and [LinkedIn](https://www.linkedin.com/in/om-prakash-sah) **for more updates.
Happy coding!
메타데이터
- post_id
- 1731ea0b59fe
- slug
- error-boundaries-in-react-a-simple-guide-to-stop-your-app-from-crashing-1731ea0b59fe
- url
- https://medium.com/@kom50/error-boundaries-in-react-a-simple-guide-to-stop-your-app-from-crashing-1731ea0b59fe
- canonical_url
- https://medium.com/@kom50/error-boundaries-in-react-a-simple-guide-to-stop-your-app-from-crashing-1731ea0b59fe
- author_url
- https://medium.com/@kom50
- status
- ok
- fetched_at
- 2026-06-12 18:14:10