React Error Boundaries: Building Resilient Applications That Don’t Crash

When building React applications, we often focus on the happy path – making sure everything works when users interact with our app as expected. But what happens when something goes wrong? A single unhandled error can crash your entire React application…


This content originally appeared on DEV Community and was authored by A0mineTV

When building React applications, we often focus on the happy path - making sure everything works when users interact with our app as expected. But what happens when something goes wrong? A single unhandled error can crash your entire React application, leaving users staring at a blank screen. This is where Error Boundaries come to the rescue.

What Are Error Boundaries ?

Error Boundaries are React components that catch JavaScript errors anywhere in their child component tree, log those errors, and display a fallback UI instead of the component tree that crashed. Think of them as a try...catch block for React components.

Error Boundaries catch errors during:

  • Rendering
  • In lifecycle methods
  • In constructors of the whole tree below them

What Error Boundaries DON'T Catch

It's important to understand the limitations:

  • Event handlers (use regular try...catch for these)
  • Asynchronous code (e.g., setTimeout or requestAnimationFrame callbacks)
  • Errors thrown during server-side rendering
  • Errors thrown in the error boundary itself

Building a Basic Error Boundary

Let's start with a simple TypeScript implementation:

import React, { Component, type ReactNode } from 'react';

interface Props {
  children: ReactNode;
  fallback?: ReactNode;
}

interface State {
  hasError: boolean;
  error?: Error;
}

class ErrorBoundary extends Component<Props, State> {
  constructor(props: Props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error: Error): State {
    // Update state so the next render will show the fallback UI
    return { hasError: true, error };
  }

  componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
    // Log the error to your error reporting service
    console.error('Error caught by ErrorBoundary:', error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      return this.props.fallback || (
        <div style={{
          padding: '20px',
          border: '1px solid #ff6b6b',
          borderRadius: '8px',
          backgroundColor: '#ffe0e0',
          color: '#d63031',
          textAlign: 'center',
          margin: '20px'
        }}>
          <h2>Oops! Something went wrong</h2>
          <p>We apologize for the inconvenience. Please try refreshing the page.</p>
          <button
            onClick={() => window.location.reload()}
            style={{
              padding: '8px 16px',
              backgroundColor: '#d63031',
              color: 'white',
              border: 'none',
              borderRadius: '4px',
              cursor: 'pointer'
            }}
          >
            Refresh Page
          </button>
        </div>
      );
    }

    return this.props.children;
  }
}

export default ErrorBoundary;

Advanced Error Boundary with Reset Functionality

For production applications, you'll want more sophisticated error handling. Here's an enhanced version:

import { Component } from "react";

type FallbackRenderArgs = {
  error: Error;
  reset: () => void;
}

type Props = {
  children: React.ReactNode;
  fallback?: React.ReactNode;
  fallbackRender?: (args: FallbackRenderArgs) => React.ReactNode;
  resetKeys?: unknown[];
  onError?: (error: Error, errorInfo: React.ErrorInfo) => void;
  onReset?: () => void;
}

type State = {
  hasError: boolean;
  error?: Error | null;
}

export default class ErrorBoundary extends Component<Props, State> {
  state: State = {hasError: false, error: null};
  private prevResetKeys: unknown[] = [];

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

  componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
    this.props.onError?.(error, errorInfo);
  }

  componentDidUpdate(prevProps: Props) {
    if (this.props.resetKeys && this.haveResetKeysChanged(prevProps.resetKeys || [], this.props.resetKeys || [])) {
      this.reset();
    }
  }

  private haveResetKeysChanged(prevKeys: unknown[], nextKeys: unknown[]) {
    return prevKeys.length !== nextKeys.length ||
           prevKeys.some((key, index) => key !== nextKeys[index]);
  }

  private reset = () => {
    this.setState({ hasError: false, error: null });
    this.props.onReset?.();
  }

  render() {
    if (this.state.hasError) {
      const {fallback, fallbackRender} = this.props;

      if (fallbackRender) {
        return fallbackRender({ error: this.state.error!, reset: this.reset });
      }

      if (fallback) {
        return fallback;
      }

      return (
        <div style={{
          padding: '20px',
          backgroundColor: '#fff3cd',
          border: '1px solid #ffeaa7',
          borderRadius: '8px',
          color: '#856404',
        }}>
          <h3>Something went wrong.</h3>
          <p>{this.state.error?.message}</p>
          <button onClick={this.reset}>Try again</button>
        </div>
      )
    }

    return this.props.children;
  }
}

Creating Custom Error Types

For better error handling, create custom error classes with additional metadata:

export class MyAppError extends Error {
  details?: {
    code?: string;
    section?: string;
    payload?: unknown;
  };

  constructor(message: string, details?: MyAppError['details']) {
    super(message);
    this.name = 'MyAppError';
    this.details = details;
  }
}

Using Error Boundaries in Practice

Here's how to implement error boundaries in your app:

function App() {
  const [count, setCount] = useState(0);

  return (
    <div>
      {/* Basic usage with default fallback */}
      <ErrorBoundary>
        <SomeComponent />
      </ErrorBoundary>

      {/* With custom fallback */}
      <ErrorBoundary
        fallback={
          <div>
            <h3>Custom Error UI</h3>
            <p>Something went wrong in this section!</p>
          </div>
        }
      >
        <AnotherComponent />
      </ErrorBoundary>

      {/* With reset functionality */}
      <ErrorBoundary
        resetKeys={[count]}
        onError={(error) => console.log('Logged to monitoring service:', error)}
        onReset={() => console.log('Boundary reset')}
        fallbackRender={({ error, reset }) => (
          <div>
            <h3>Error: {error.name}</h3>
            <p>{error.message}</p>
            <button onClick={reset}>Try Again</button>
          </div>
        )}
      >
        <BuggyComponent />
      </ErrorBoundary>
    </div>
  );
}

Best Practices

1. Strategic Placement

Place error boundaries at strategic points in your component tree:

  • Around route components
  • Around major UI sections
  • Around third-party components

2. Granular Error Handling

Don't wrap your entire app in a single error boundary. Use multiple boundaries for different sections:

function App() {
  return (
    <div>
      <Header /> {/* Not wrapped - header errors shouldn't crash navigation */}

      <ErrorBoundary>
        <Navigation />
      </ErrorBoundary>

      <main>
        <ErrorBoundary>
          <MainContent />
        </ErrorBoundary>
      </main>

      <ErrorBoundary>
        <Sidebar />
      </ErrorBoundary>
    </div>
  );
}

3. Meaningful Error Messages

Provide helpful error messages and recovery options:

<ErrorBoundary
  fallbackRender={({ error, reset }) => (
    <div>
      <h2>Unable to load comments</h2>
      <p>There was a problem loading the comments section.</p>
      <button onClick={reset}>Retry</button>
      <button onClick={() => window.location.reload()}>
        Refresh Page
      </button>
    </div>
  )}
>
  <CommentsSection />
</ErrorBoundary>

4. Error Reporting

Always log errors to your monitoring service:

<ErrorBoundary
  onError={(error, errorInfo) => {
    // Log to your error monitoring service
    errorReportingService.captureException(error, {
      extra: errorInfo,
      tags: { boundary: 'comments-section' }
    });
  }}
>
  <CommentsSection />
</ErrorBoundary>

Testing Error Boundaries

Create a component for testing error scenarios:

function BuggyComponent({ shouldThrowError = false }: { shouldThrowError?: boolean }) {
  const [explode, setExplode] = useState(false);

  if (shouldThrowError && explode) {
    throw new Error('Boom! 💥');
  }

  return (
    <div>
      <p>I'm a potentially unstable component...</p>
      <button onClick={() => setExplode(true)}>
        Trigger Error
      </button>
    </div>
  );
}

React 18 and Concurrent Features

With React 18's concurrent features, error boundaries work seamlessly with:

  • Suspense boundaries
  • Concurrent rendering
  • Automatic batching

Make sure your error boundaries are compatible with these features by avoiding side effects in render methods.

Alternatives and Complementary Patterns

1. React Query Error Handling

For data fetching errors, React Query provides excellent error handling:

function DataComponent() {
  const { data, error, isError } = useQuery('userData', fetchUser);

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

  return <div>{data?.name}</div>;
}

2. Error Context

For global error state management:

const ErrorContext = createContext<{
  reportError: (error: Error) => void;
}>({
  reportError: () => {}
});

function ErrorProvider({ children }: { children: ReactNode }) {
  const reportError = useCallback((error: Error) => {
    // Send to error reporting service
    console.error('Global error:', error);
  }, []);

  return (
    <ErrorContext.Provider value={{ reportError }}>
      {children}
    </ErrorContext.Provider>
  );
}

Conclusion

Error boundaries are essential for building robust React applications. They provide a safety net that prevents your entire app from crashing due to unexpected errors, while giving you the opportunity to:

  • Display meaningful error messages to users
  • Log errors for debugging and monitoring
  • Provide recovery mechanisms
  • Maintain a better user experience

By implementing error boundaries strategically throughout your application, you create a more resilient user experience that gracefully handles the unexpected.

Key Takeaways

  1. Always use error boundaries in production React applications
  2. Place them strategically - not just around your entire app
  3. Provide meaningful fallback UIs with recovery options
  4. Log errors to your monitoring service for debugging
  5. Test error scenarios during development
  6. Combine with other error handling patterns for comprehensive coverage

Remember: Error boundaries are about graceful degradation, not preventing errors. Focus on providing the best possible user experience when things go wrong.


This content originally appeared on DEV Community and was authored by A0mineTV


Print Share Comment Cite Upload Translate Updates
APA

A0mineTV | Sciencx (2025-09-15T16:43:13+00:00) React Error Boundaries: Building Resilient Applications That Don’t Crash. Retrieved from https://www.scien.cx/2025/09/15/react-error-boundaries-building-resilient-applications-that-dont-crash/

MLA
" » React Error Boundaries: Building Resilient Applications That Don’t Crash." A0mineTV | Sciencx - Monday September 15, 2025, https://www.scien.cx/2025/09/15/react-error-boundaries-building-resilient-applications-that-dont-crash/
HARVARD
A0mineTV | Sciencx Monday September 15, 2025 » React Error Boundaries: Building Resilient Applications That Don’t Crash., viewed ,<https://www.scien.cx/2025/09/15/react-error-boundaries-building-resilient-applications-that-dont-crash/>
VANCOUVER
A0mineTV | Sciencx - » React Error Boundaries: Building Resilient Applications That Don’t Crash. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2025/09/15/react-error-boundaries-building-resilient-applications-that-dont-crash/
CHICAGO
" » React Error Boundaries: Building Resilient Applications That Don’t Crash." A0mineTV | Sciencx - Accessed . https://www.scien.cx/2025/09/15/react-error-boundaries-building-resilient-applications-that-dont-crash/
IEEE
" » React Error Boundaries: Building Resilient Applications That Don’t Crash." A0mineTV | Sciencx [Online]. Available: https://www.scien.cx/2025/09/15/react-error-boundaries-building-resilient-applications-that-dont-crash/. [Accessed: ]
rf:citation
» React Error Boundaries: Building Resilient Applications That Don’t Crash | A0mineTV | Sciencx | https://www.scien.cx/2025/09/15/react-error-boundaries-building-resilient-applications-that-dont-crash/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.