Centralized Error Handling in React JS

I wanted to simplify error handling across my entire React JS app without writing try/catch blocks everywhere. It feels messy to handle errors ad hoc in every component or event handler. So, I started wondering if there’s a way to manage all errors in one place, like how Express uses a centralized error-handling middleware. My goal was to create a robust, systematic way to catch all thrown errors—whether from HTTP requests or other async operations—without repeating code.

Here’s my React JS Code:

codeimport React JS, { useState } from "react";
import axios from "axios";

const MyComponent: React.FC = () => {
const [name, setName] = useState("");

const handleSubmit = async (event: React.SyntheticEvent) => {
event.preventDefault();

// I want to avoid repeating try/catch everywhere
try {
await axios.post("/api/users/", { name });
} catch (error) {
// How can I handle this systematically across the app
console.error(error);
}
};

return (
<form>
<input
name="name"
type="text"
onChange={(e) => setName(e.target.value)}
value={name}
/>
<button onClick={handleSubmit}>Submit form</button>
</form>
);
};

export default MyComponent;

What I really want is a way to catch all errors in one place whether it’s a network issue, a failed async operation, or even some unexpected issue within components. Something like middleware would be perfect, but I’m not sure how to do it properly in React.

If you’ve encountered or implemented any consistent error management strategy like this,

Correct Code:

codeimport React JS, { useState } from "react";
import axios from "axios";

// Define props if needed
interface Props {}

// Unified error handler utility
const handleError = (error: unknown) => {
// Unified logging (could log to external service or console)
console.error("An error occurred:", error);

// Example: Send to an error reporting service
// errorReportingService.log(error);
};

// Higher-order function to wrap async functions with error handling
const withErrorHandling = (fn: () => Promise<void>) => async () => {
try {
await fn();
} catch (error) {
handleError(error);
}
};

// Main component
const MyComponent: React.FC<Props> = () => {
const [name, setName] = useState("");

// Handle form submission with async logic
const handleSubmit = withErrorHandling(async () => {
await axios.post("/api/users/", { name });
});

return (
<form onSubmit={(e) => e.preventDefault()}>
<input
name="name"
type="text"
onChange={(e) => setName(e.target.value)}
value={name}
/>
<button onClick={handleSubmit}>Submit form</button>
</form>
);
};

export default MyComponent;

Explanation

Error Handling with a Centralized Function:

  • The handleError function ensures that all errors are processed in the same way (e.g., logged to the console or sent to an external error-tracking service). This avoids ad-hoc logging spread throughout the codebase.
    codeconst handleError = (error: unknown) => {
    console.error("An error occurred:", error);
    };

    Reusable Error Handling Wrapper:

    • The withErrorHandling function is a higher-order function that wraps any asynchronous function and automatically applies try-catch logic. This prevents the need to manually write try-catch blocks for every API call.
     codeconst withErrorHandling = (fn: () => Promise<void>) => async () => {
    try {
    await fn();
    } catch (error) {
    handleError(error);
    }
    };

    Handling Form Submission with Error Handling:

    • The handleSubmit function is wrapped using withErrorHandling. If it throws an error (e.g., network error from axios), it will be automatically caught and logged via the handleError function.
    codeconst handleSubmit = withErrorHandling(async () => {
    await axios.post("/api/users/", { name });
    });

    Preventing Default Form Behavior:

    • We use e.preventDefault() in the <form> tag to prevent the page from reloading on submission.
    <form onSubmit={(e) => e.preventDefault()}>

    Benefits:

    Cleaner Code: Reduces repetition and keeps the code DRY (Don’t Repeat Yourself).

    Centralized Error Handling: Avoids scattering try-catch logic and inconsistent error reporting across the code.

    Reusable Higher-Order Function: Easily wraps any async function with consistent error handling.

    Related blog posts