Welcome back to the React Mastery Series!
In the previous article, we explored React Design Patterns and learned how enterprise applications use:
- Composition
- Compound Components
- Custom Hooks
- Higher-Order Components (HOCs)
- Provider Pattern
- Feature-Based Architecture
Today, we'll learn how to build React applications that can gracefully recover from unexpected failures.
React Error Handling
No application is perfect.
Servers fail.
Networks disconnect.
APIs return unexpected responses.
Developers accidentally introduce bugs.
The goal isn't to eliminate every error—it's to ensure users continue to have a good experience when errors occur.
Why Error Handling Matters
Imagine a customer opening an internet banking application.
Dashboard
↓
Account Summary
↓
Transactions
↓
Investments
↓
One Component Crashes
↓
Entire Application Becomes Blank
This creates a poor user experience.
Instead, we'd rather display:
Dashboard
↓
Account Summary
↓
Transactions
↓
Investment Widget Crashes
↓
Show Friendly Error Message
↓
Rest of Dashboard Continues Working
This approach makes applications more resilient.
Types of Errors in React
React applications commonly encounter four categories of errors.
| Error Type | Example |
|---|---|
| Rendering Errors | Component crashes while rendering |
| JavaScript Errors | Undefined variables, null references |
| Network Errors | API request failures |
| Runtime Errors | Unexpected user interactions or invalid data |
Each category requires a different handling strategy.
What Happens When a Component Crashes?
Consider this component:
function UserProfile() {
const user = null;
return <h2>{user.name}</h2>;
}
When rendered:
Cannot read properties of null
Without protection, React may unmount the affected part of the application.
Introducing Error Boundaries
An Error Boundary catches rendering errors in its child component tree.
Application
│
▼
Error Boundary
│
▼
Dashboard
│
▼
Profile Component Crashes
│
▼
Fallback UI Appears
Instead of a blank page, users see a meaningful message.
Creating an Error Boundary
Error Boundaries are currently implemented using class components.
import React from "react";
type ErrorBoundaryState = {
hasError: boolean;
};
class ErrorBoundary extends React.Component<
React.PropsWithChildren,
ErrorBoundaryState
> {
state: ErrorBoundaryState = {
hasError: false,
};
static getDerivedStateFromError() {
return {
hasError: true,
};
}
componentDidCatch(
error: Error,
errorInfo: React.ErrorInfo
) {
console.error(error, errorInfo);
}
render() {
if (this.state.hasError) {
return <h2>Something went wrong.</h2>;
}
return this.props.children;
}
}
export default ErrorBoundary;
Although modern React primarily uses function components, Error Boundaries still rely on class components.
Using an Error Boundary
Wrap components that could fail.
<ErrorBoundary>
<Dashboard />
</ErrorBoundary>
Or wrap only critical sections.
<Dashboard>
<ErrorBoundary>
<InvestmentWidget />
</ErrorBoundary>
<TransactionList />
</Dashboard>
If the investment widget crashes, the transaction list continues working.
What Error Boundaries Catch
✅ Rendering errors
✅ Constructor errors
✅ Lifecycle method errors
They help prevent the application from crashing completely.
What Error Boundaries Don't Catch
Error Boundaries do not catch:
- Event handler errors
- Async errors
- API failures
-
setTimeout()errors - Server-side rendering errors
These require different handling strategies.
Handling API Errors
Always anticipate API failures.
async function fetchUsers() {
try {
const response = await api.get("/users");
return response.data;
} catch (error) {
console.error(error);
throw error;
}
}
In the UI:
if (error) {
return (
<ErrorMessage
message="Unable to load users."
/>
);
}
Show users a helpful message instead of exposing technical details.
Handling Async Errors
Async functions should always use try...catch.
async function handleSubmit() {
try {
await saveProfile();
} catch (error) {
setError(
"Unable to save your profile."
);
}
}
Gracefully handling failures improves the user experience.
Defensive Rendering
Avoid assuming data always exists.
Instead of:
<h2>{user.name}</h2>
Use optional chaining.
<h2>{user?.name}</h2>
Or provide a fallback.
<h2>{user?.name ?? "Guest User"}</h2>
This prevents many runtime errors.
Global Axios Error Handling
Centralize API error handling with interceptors.
api.interceptors.response.use(
(response) => response,
(error) => {
if (error.response?.status === 401) {
// Redirect to login
}
if (error.response?.status === 500) {
console.error("Server error");
}
return Promise.reject(error);
}
);
This avoids duplicating error handling across the application.
Logging Errors
Errors should be logged for developers.
Typical information includes:
- Error message
- Stack trace
- Browser
- User ID (if available)
- URL
- Timestamp
Example:
Error
↓
Log Service
↓
Developer Dashboard
↓
Fix Bug
Logging helps identify issues that users encounter in production.
Monitoring with Sentry
Many enterprise applications use monitoring platforms such as Sentry to collect production errors.
Example setup:
import * as Sentry from "@sentry/react";
Sentry.init({
dsn: "YOUR_DSN",
});
These tools automatically collect error details and help teams prioritize fixes.
Building a Friendly Fallback UI
Avoid showing messages like:
TypeError:
Cannot read properties of undefined
Instead, display:
Oops!
Something went wrong.
Please refresh the page or try again later.
Users don't need technical stack traces.
They need clear guidance.
Retry Pattern
Network issues are often temporary.
Instead of:
API Failed
↓
Stop
Prefer:
API Failed
↓
Show Retry Button
↓
Retry Request
↓
Success
This improves resilience for intermittent failures.
Real-World Banking Example
Imagine the transaction history service is temporarily unavailable.
Instead of crashing the dashboard:
Dashboard
↓
Accounts ✓
Cards ✓
Loans ✓
Transactions ✗
↓
Unable to load transactions.
[ Retry ]
Users can still access the rest of the application.
Folder Structure
A scalable error handling structure:
src
├── components
│ ├── ErrorBoundary.tsx
│ ├── ErrorMessage.tsx
│ └── RetryButton.tsx
├── services
│ └── api.ts
├── hooks
└── useErrorHandler.ts
Keep error handling reusable and centralized.
Common Mistakes
1. Ignoring API Failures
Every API call can fail.
Always handle:
- Loading
- Success
- Error
2. Showing Technical Errors to Users
Avoid exposing stack traces or exception messages.
Display user-friendly messages instead.
3. Wrapping the Entire App with One Error Boundary
A single Error Boundary can cause the entire application to display fallback UI.
Use multiple boundaries around critical sections when appropriate.
4. Not Logging Errors
If errors aren't logged, developers may never know users experienced them.
Always collect enough information to investigate production issues.
Best Practices
- Use Error Boundaries around major UI sections.
- Handle async operations with
try...catch. - Use optional chaining when accessing nested data.
- Centralize API error handling.
- Display meaningful fallback messages.
- Log errors to a monitoring service.
- Allow users to retry failed operations whenever possible.
Key Takeaways
Today, we learned:
✅ Error Boundaries prevent rendering errors from crashing the entire application.
✅ Async errors require try...catch.
✅ API failures should display helpful messages and retry options.
✅ Centralized error handling reduces duplicate code.
✅ Logging production errors helps teams identify and fix issues quickly.
✅ Resilient applications continue functioning even when individual components fail.
Coming Next 🚀
In Day 30, we will explore one of the most requested topics by React developers:
React Project Folder Structure & Scalable Architecture
We will learn:
- Small vs Large Project Structure
- Feature-Based Architecture
- Shared Components
- Services Layer
- Hooks Organization
- State Management Structure
- Environment Configuration
- Enterprise Folder Organization
By the end of the next article, you'll know how to organize React applications the way large engineering teams at companies like banks, fintech firms, and SaaS organizations structure their production codebases.
Happy Coding! 🚀
Top comments (0)