DEV Community

Cover image for Designing a Resilient UI: Advanced Patterns and Accessibility for Error Handling in React
Stanley J
Stanley J

Posted on

2

Designing a Resilient UI: Advanced Patterns and Accessibility for Error Handling in React

Follow Up

Building a resilient user interface goes beyond just displaying error messages. In this follow-up, we’ll explore advanced error boundary patterns in React, strategies for global error handling, and accessibility considerations for inclusive fallback UI designs. Let’s dive in!


Advanced Error Boundary Patterns

React 19 introduces a built-in ErrorBoundary component, making it easier to handle errors without creating custom classes. This simplifies error handling while aligning with modern React practices.

Using React’s Built-In Error Boundary

The new ErrorBoundary component is a functional, declarative way to catch and handle errors in the component tree:

import { ErrorBoundary } from 'react';

const FallbackComponent = ({ error, resetErrorBoundary }) => (
  <div>
    <h2>Oops! An error occurred.</h2>
    <p>{error.message}</p>
    <button onClick={resetErrorBoundary}>Try Again</button>
  </div>
);

const App = () => (
  <ErrorBoundary 
    FallbackComponent={FallbackComponent} 
    onError={(error, info) => console.error('ErrorBoundary caught an error:', error, info)}
  >
    <MyComponent />
  </ErrorBoundary>
);
Enter fullscreen mode Exit fullscreen mode

Key Features

  • FallbackComponent: Provides a declarative way to render fallback UI.
  • resetErrorBoundary: Allows you to reset the error state, often used for retry mechanisms.
  • onError Callback: Captures error details and logs them for debugging or reporting.

This built-in solution removes the need for custom class-based implementations, ensuring consistency and ease of use.


Global Error Handling

As applications grow, it becomes essential to handle errors globally to prevent edge cases from slipping through. JavaScript provides global event listeners that allow you to handle these errors at the application level. Here’s how to centralize error handling effectively:

Capturing Uncaught Errors and Rejections

Leverage global event listeners to catch unhandled errors:

// Capture uncaught JavaScript errors
window.onerror = (message, source, lineno, colno, error) => {
  console.error("Global Error Caught:", { message, source, lineno, colno, error });
};

// Capture unhandled promise rejections
window.onunhandledrejection = (event) => {
  console.error("Unhandled Promise Rejection:", event.reason);
};

Enter fullscreen mode Exit fullscreen mode

Explanation - window.onerror:

  • message: The error message that describes the problem.
  • source: The URL of the script where the error occurred.
  • lineno: The line number in the script where the error occurred.
  • colno: The column number where the error occurred.
  • error: The actual error object (if available), which can provide further details about the issue.

This allows you to log relevant error information that can help with debugging. The console.error output can be replaced with custom error handling mechanisms, such as sending logs to your server or tracking error statistics.

Explanation - window.onunhandledrejection:

  • event.reason: This property contains the reason or error object associated with the unhandled rejection. Typically, it will be the error message or exception thrown in the promise.

This global listener ensures that any unhandled rejections are captured and logged. It’s a helpful way to make sure your asynchronous code behaves predictably, and it provides a way to identify and address potential issues caused by unhandled promise rejections.


Accessibility Considerations

Ensuring fallback UIs are accessible helps improve usability for all users, including those with disabilities.

Announcing Errors with ARIA

Use ARIA live regions to announce errors to screen readers dynamically:

const AccessibleErrorMessage = ({ error }) => (
  <div role="alert" aria-live="assertive" style={{ color: "red" }}>
    {error}
  </div>
);

const App = () => {
  const [error, setError] = React.useState(null);

  return (
    <div>
      {error && <AccessibleErrorMessage error={error} />}
      <button onClick={() => setError("An unexpected error occurred!")}>Trigger Error</button>
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

Focus Management

When an error occurs, direct focus to the error message for easier navigation:

const ErrorWithFocus = ({ error }) => {
  const errorRef = React.useRef();

  React.useEffect(() => {
    if (error && errorRef.current) {
      errorRef.current.focus();
    }
  }, [error]);

  return (
    <div
      ref={errorRef}
      tabIndex="-1"
      role="alert"
      aria-live="assertive"
      style={{ color: "red" }}
    >
      {error}
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

Wrapping Up

By leveraging React 19’s built-in ErrorBoundary component, implementing Global error handling, and prioritizing Accessibility, you can create UIs that gracefully handle failures while catering to a diverse user base. Remember, resilience in UI design isn’t just about recovering from errors—it’s about building trust with your users.

What’s your approach to handling errors in your applications?

Image of Timescale

🚀 pgai Vectorizer: SQLAlchemy and LiteLLM Make Vector Search Simple

We built pgai Vectorizer to simplify embedding management for AI applications—without needing a separate database or complex infrastructure. Since launch, developers have created over 3,000 vectorizers on Timescale Cloud, with many more self-hosted.

Read full post →

Top comments (0)

The best way to debug slow web pages cover image

The best way to debug slow web pages

Tools like Page Speed Insights and Google Lighthouse are great for providing advice for front end performance issues. But what these tools can’t do, is evaluate performance across your entire stack of distributed services and applications.

Watch video