← Back to list

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…

om prakash · 2025-12-06 09:21 · 1 claps · 5.7 min read
#react #react-error-boundary #error-handling
Open on Medium ↗
Wiki topics: 🌐 · Web Development 🧠 · Mental Wellness

Error Boundaries in React: A Simple Guide to Stop Your App From Crashing

Error Boundaries in React

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:

  1. static getDerivedStateFromError() — Renders fallback UI
  2. componentDidCatch() — 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?

  1. UserProfile throws an error → Error Boundary catches it → Shows fallback UI
  2. CommentSection works fine → Renders normally
  3. 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

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