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?

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay