Centralized Error Handling with React useContext

In my React application, I have to fetch the backend to:

  • list the products
  • display the price
  • list the availabilities
  • and more.

But all the time, if an error occurs, a 4xx or a 5xx, I repeat the same logic, everywhere: display a pop-in with an error message like “Your product is no more available”, “There is no availabilities in the selected dates” or simply “An internal error occurred, try again later.”

But it’s always the same logic. I don’t want to repeat the popin and the catch of the request everywhere. That’s why I use the useContext hook.

What is the useContext Hook?

At its core, useContext is a React hook that lets you subscribe to React Context. Think of Context as a way to pass data through your component tree without having to pass props down manually at every level. It’s particularly useful for “global” data like user authentication, themes, or in my case, error states, that many components might need.

Defining the Error Context

Let’s first set up the error context. This context will hold the errorMessage, a catchError function to set the error message, and a clearError function to dismiss it. I’ll also export a custom hook, useError, for easy consumption.

Here’s what the ErrorContext.js file might look like:

import React, { createContext, useContext, useState } from 'react';

const ErrorContext = createContext(null);

export const ErrorProvider = ({ children }) => {
  const [errorMessage, setErrorMessage] = useState(null);

  const catchError = (message) => {
    setErrorMessage(message);
  };

  const clearError = () => {
    setErrorMessage(null);
  };

  const contextValue = {
    errorMessage,
    catchError,
    clearError,
  };

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

export const useError = () => {
  const context = useContext(ErrorContext);
  if (!context) {
    throw new Error('useError must be used within an ErrorProvider');
  }
  return context;
};

I’ve created ErrorContext, a provider component ErrorProvider to wrap my application, and a convenient useError custom hook for components to access the error state and functions.

Building the Error Pop-in Component

Next, I need a component to display the error message. This ErrorPopin component will listen to the errorMessage from my ErrorContext and render itself only when an error is present. It will also include a “Close” button to clearError.

import React from 'react';
import { useError } from './ErrorContext';

const ErrorPopin = () => {
  const { errorMessage, clearError } = useError();

  if (!errorMessage) {
    return null;
  }

  return (
    <div style={{
      position: 'fixed',
      top: '20px',
      right: '20px',
      backgroundColor: '#ffdddd',
      border: '1px solid #ffaaaa',
      padding: '15px',
      borderRadius: '5px',
      boxShadow: '0 2px 10px rgba(0,0,0,0.1)',
      zIndex: 1000,
      color: '#d8000c'
    }}>
      <p>Error: {errorMessage}</p>
      <button
        onClick={clearError}
        style={{
          marginTop: '10px',
          padding: '8px 15px',
          backgroundColor: '#f44336',
          color: 'white',
          border: 'none',
          borderRadius: '4px',
          cursor: 'pointer'
        }}
      >
        Close
      </button>
    </div>
  );
};

export default ErrorPopin;

This ErrorPopin is designed to be simple, appearing only when an error is set and disappearing once cleared.

Integrating into The Application Root (App.js)

To make the error context and pop-in available globally, I need to wrap my main application components with the ErrorProvider and render the ErrorPopin component. Typically, this happens in the App.js file.

import React from 'react';
import { ErrorProvider } from './ErrorContext';
import ErrorPopin from './ErrorPopin';
import SomeComponent from './SomeComponent';
import AnotherComponent from './AnotherComponent';

function App() {
  return (
    <ErrorProvider>
      <ErrorPopin />
      <div className="App">
        <h1>My Awesome App</h1>
        <SomeComponent />
        <AnotherComponent />
      </div>
    </ErrorProvider>
  );
}

export default App;

Now, any component rendered inside ErrorProvider can access the error context, and the ErrorPopin will automatically display global errors.

Consuming the Error Context in Impacted Components

Finally, let’s see how an individual components will use the useError hook to catchError when fetch requests fail.

import React, { useEffect, useState } from 'react';
import { useError } from './ErrorContext';

const SomeComponent = () => {
  const { catchError } = useError();
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    setLoading(true);
    fetch('https://api.example.com/data')
      .then(response => {
        if (!response.ok) {
          const errorData = await response.json();
          throw new Error(errorData.message);
        }
        return reponse.json();
      })
      .then(result => setData(result))
      .catch(catchError)
      .finally(() => setLoading(false))
  }, []);

  if (loading) {
    return <p>Loading data...</p>;
  }

  return (
    <div>
      <h2>Data from API</h2>
      {data ? <pre>{JSON.stringify(data, null, 2)}</pre> : <p>No data loaded.</p>}
    </div>
  );
};

export default SomeComponent;

Now, in my SomeComponent I simply import useError and call catchError(error.message) within its catch block. This keeps the component’s error handling logic minimal and delegates the display logic to my centralized ErrorPopin. This pattern significantly reduces complexity and makes my error management easier to maintain.

I use the useContext in many other cases like the authentication of theme selection. Every time I need to share an information across the component tree.


Never Miss Another Tech Innovation

Concrete insights and actionable resources delivered straight to your inbox to boost your developer career.

My New ebook, Best Practices To Create A Backend With Spring Boot 3, is available now.

Best practices to create a backend with Spring Boot 3

Leave a comment

Discover more from The Dev World - Sergio Lema

Subscribe now to keep reading and get access to the full archive.

Continue reading