Error Handling Resilience Implementation

1. React Error Boundaries Fallback UI

Concept Scope Catches Doesn't Catch
Error Boundary Component tree below it Render errors, lifecycle methods, constructors Event handlers, async code, SSR, errors in boundary itself
componentDidCatch Class component method Error object, error info with stack Used for side effects (logging)
getDerivedStateFromError Static lifecycle method Returns state to render fallback Pure function, no side effects
react-error-boundary BEST Third-party library Hooks support, reset, FallbackComponent More features than built-in

Example: Error Boundaries Implementation

// Basic Error Boundary (class component)
class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false, error: null };
  }
  
  static getDerivedStateFromError(error) {
    // Update state to render fallback UI
    return { hasError: true, error };
  }
  
  componentDidCatch(error, errorInfo) {
    // Log error to service
    console.error('Error caught by boundary:', error, errorInfo);
    logErrorToService(error, errorInfo);
  }
  
  render() {
    if (this.state.hasError) {
      return (
        <div className="error-fallback">
          <h2>Something went wrong</h2>
          <p>{this.state.error?.message}</p>
          <button onClick={() => this.setState({ hasError: false })}>
            Try again
          </button>
        </div>
      );
    }
    
    return this.props.children;
  }
}

// Usage
function App() {
  return (
    <ErrorBoundary>
      <MyComponent />
    </ErrorBoundary>
  );
}

// react-error-boundary library (recommended)
npm install react-error-boundary

import { ErrorBoundary } from 'react-error-boundary';

function ErrorFallback({ error, resetErrorBoundary }) {
  return (
    <div role="alert">
      <h2>Something went wrong</h2>
      <pre style={{ color: 'red' }}>{error.message}</pre>
      <button onClick={resetErrorBoundary}>Try again</button>
    </div>
  );
}

function App() {
  return (
    <ErrorBoundary
      FallbackComponent={ErrorFallback}
      onReset={() => {
        // Reset app state
        window.location.reload();
      }}
      onError={(error, errorInfo) => {
        // Log to error reporting service
        logErrorToSentry(error, errorInfo);
      }}
    >
      <MyApp />
    </ErrorBoundary>
  );
}

// Multiple error boundaries (granular)
function App() {
  return (
    <ErrorBoundary FallbackComponent={AppErrorFallback}>
      <Header />
      
      <ErrorBoundary FallbackComponent={SidebarErrorFallback}>
        <Sidebar />
      </ErrorBoundary>
      
      <ErrorBoundary FallbackComponent={MainErrorFallback}>
        <MainContent />
      </ErrorBoundary>
    </ErrorBoundary>
  );
}

// Error boundary with reset keys
function UserProfile({ userId }) {
  return (
    <ErrorBoundary
      FallbackComponent={ErrorFallback}
      resetKeys={[userId]} // Reset when userId changes
    >
      <UserData userId={userId} />
    </ErrorBoundary>
  );
}

// Custom error boundary hook
function useErrorHandler() {
  const [error, setError] = useState(null);
  
  useEffect(() => {
    if (error) {
      throw error; // Will be caught by Error Boundary
    }
  }, [error]);
  
  return setError;
}

// Usage in async code
function MyComponent() {
  const handleError = useErrorHandler();
  
  const fetchData = async () => {
    try {
      const data = await api.getData();
      setData(data);
    } catch (error) {
      handleError(error); // Throw to Error Boundary
    }
  };
  
  return <button onClick={fetchData}>Fetch Data</button>;
}

// Error boundary with different fallbacks per error type
function ErrorFallback({ error, resetErrorBoundary }) {
  if (error.name === 'ChunkLoadError') {
    return (
      <div>
        <h2>New version available</h2>
        <button onClick={() => window.location.reload()}>
          Reload to update
        </button>
      </div>
    );
  }
  
  if (error.message.includes('Network')) {
    return (
      <div>
        <h2>Network Error</h2>
        <p>Check your internet connection</p>
        <button onClick={resetErrorBoundary}>Retry</button>
      </div>
    );
  }
  
  return (
    <div>
      <h2>Application Error</h2>
      <details>
        <summary>Error details</summary>
        <pre>{error.stack}</pre>
      </details>
      <button onClick={resetErrorBoundary}>Try again</button>
    </div>
  );
}

// Nested error boundaries with context
const ErrorContext = createContext();

function RootErrorBoundary({ children }) {
  const [errors, setErrors] = useState([]);
  
  const logError = (error, errorInfo) => {
    setErrors(prev => [...prev, { error, errorInfo, timestamp: Date.now() }]);
  };
  
  return (
    <ErrorContext.Provider value={{ errors, logError }}>
      <ErrorBoundary
        FallbackComponent={RootErrorFallback}
        onError={logError}
      >
        {children}
      </ErrorBoundary>
    </ErrorContext.Provider>
  );
}

// Testing error boundaries
// MyComponent.test.tsx
import { render, screen } from '@testing-library/react';
import { ErrorBoundary } from 'react-error-boundary';

const ThrowError = () => {
  throw new Error('Test error');
};

test('shows error fallback', () => {
  const consoleSpy = jest.spyOn(console, 'error').mockImplementation();
  
  render(
    <ErrorBoundary FallbackComponent={ErrorFallback}>
      <ThrowError />
    </ErrorBoundary>
  );
  
  expect(screen.getByText(/something went wrong/i)).toBeInTheDocument();
  
  consoleSpy.mockRestore();
});

// Global error handler for unhandled errors
window.addEventListener('error', (event) => {
  console.error('Unhandled error:', event.error);
  logErrorToService(event.error);
});

window.addEventListener('unhandledrejection', (event) => {
  console.error('Unhandled promise rejection:', event.reason);
  logErrorToService(event.reason);
});

// Suspense with Error Boundary
function App() {
  return (
    <ErrorBoundary FallbackComponent={ErrorFallback}>
      <Suspense fallback={<Loading />}>
        <LazyComponent />
      </Suspense>
    </ErrorBoundary>
  );
}

// Error boundary for specific routes (React Router)
function AppRoutes() {
  return (
    <Routes>
      <Route
        path="/"
        element={
          <ErrorBoundary FallbackComponent={HomeErrorFallback}>
            <Home />
          </ErrorBoundary>
        }
      />
      <Route
        path="/dashboard"
        element={
          <ErrorBoundary FallbackComponent={DashboardErrorFallback}>
            <Dashboard />
          </ErrorBoundary>
        }
      />
    </Routes>
  );
}

Error Boundary Placement

  • Root: Catch all app errors
  • Route: Per-route error handling
  • Component: Isolate feature errors
  • Widget: Third-party widget failures
  • Lazy: Code splitting errors
  • Form: Form submission errors

Error Boundary Checklist

Limitation: Error boundaries don't catch errors in event handlers, async code, SSR, or the boundary itself. Use try-catch for those.

2. Sentry LogRocket Error Monitoring

Tool Type Features Use Case
Sentry BEST Error tracking Stack traces, breadcrumbs, releases, performance Real-time error monitoring, source maps, alerts
LogRocket Session replay Video replay, console logs, network, Redux Debug user sessions, understand context
Rollbar Error tracking Real-time alerts, telemetry, people tracking Similar to Sentry, alternative option
Bugsnag Error monitoring Stability scoring, release health Mobile + web error tracking

Example: Error Monitoring Setup

// Sentry setup
npm install @sentry/react @sentry/tracing

// index.tsx
import * as Sentry from '@sentry/react';
import { BrowserTracing } from '@sentry/tracing';

Sentry.init({
  dsn: 'YOUR_SENTRY_DSN',
  environment: process.env.NODE_ENV,
  integrations: [
    new BrowserTracing(),
    new Sentry.Replay({
      maskAllText: false,
      blockAllMedia: false,
    })
  ],
  
  // Performance monitoring
  tracesSampleRate: 1.0, // 100% in dev, 0.1 (10%) in prod
  
  // Session replay
  replaysSessionSampleRate: 0.1, // 10% of sessions
  replaysOnErrorSampleRate: 1.0, // 100% of errors
  
  // Release tracking
  release: process.env.REACT_APP_VERSION,
  
  // Filter errors
  beforeSend(event, hint) {
    // Don't send to Sentry in dev
    if (process.env.NODE_ENV === 'development') {
      return null;
    }
    
    // Filter out specific errors
    if (event.exception?.values?.[0]?.value?.includes('ResizeObserver')) {
      return null;
    }
    
    return event;
  }
});

// Wrap app with Sentry
const container = document.getElementById('root');
const root = createRoot(container);

root.render(
  <Sentry.ErrorBoundary fallback={ErrorFallback} showDialog>
    <App />
  </Sentry.ErrorBoundary>
);

// React Router integration
import {
  createRoutesFromChildren,
  matchRoutes,
  useLocation,
  useNavigationType,
} from 'react-router-dom';

Sentry.init({
  integrations: [
    new BrowserTracing({
      routingInstrumentation: Sentry.reactRouterV6Instrumentation(
        React.useEffect,
        useLocation,
        useNavigationType,
        createRoutesFromChildren,
        matchRoutes
      )
    })
  ]
});

// Manual error reporting
try {
  somethingThatMightFail();
} catch (error) {
  Sentry.captureException(error);
}

// Add context to errors
Sentry.setUser({
  id: user.id,
  email: user.email,
  username: user.username
});

Sentry.setTag('page_locale', 'en-us');
Sentry.setContext('character', {
  name: 'Mighty Fighter',
  level: 19,
  gold: 12300
});

// Breadcrumbs (automatic + manual)
Sentry.addBreadcrumb({
  category: 'auth',
  message: 'User logged in',
  level: 'info'
});

// Capture custom messages
Sentry.captureMessage('Something important happened', 'warning');

// Performance monitoring
const transaction = Sentry.startTransaction({
  name: 'API Request',
  op: 'http.client'
});

fetch('/api/data')
  .then(response => response.json())
  .finally(() => transaction.finish());

// React profiler integration
import { Profiler } from '@sentry/react';

function App() {
  return (
    <Profiler name="App">
      <MyComponent />
    </Profiler>
  );
}

// LogRocket setup
npm install logrocket logrocket-react

import LogRocket from 'logrocket';
import setupLogRocketReact from 'logrocket-react';

LogRocket.init('YOUR_APP_ID', {
  console: {
    shouldAggregateConsoleErrors: true
  },
  network: {
    requestSanitizer: (request) => {
      // Remove sensitive data
      if (request.headers['Authorization']) {
        request.headers['Authorization'] = '[REDACTED]';
      }
      return request;
    }
  },
  dom: {
    inputSanitizer: true // Mask input values
  }
});

setupLogRocketReact(LogRocket);

// Identify user
LogRocket.identify(user.id, {
  name: user.name,
  email: user.email,
  subscriptionType: 'pro'
});

// Track custom events
LogRocket.track('Checkout Completed', {
  orderId: '12345',
  total: 99.99
});

// Sentry + LogRocket integration
LogRocket.getSessionURL((sessionURL) => {
  Sentry.configureScope((scope) => {
    scope.setExtra('sessionURL', sessionURL);
  });
});

// Add LogRocket to Sentry events
Sentry.init({
  beforeSend(event) {
    if (event.exception) {
      const sessionURL = LogRocket.sessionURL;
      event.extra = {
        ...event.extra,
        LogRocket: sessionURL
      };
    }
    return event;
  }
});

// Redux integration
import LogRocket from 'logrocket';

const store = createStore(
  reducer,
  applyMiddleware(LogRocket.reduxMiddleware())
);

// Source maps for Sentry
// package.json
{
  "scripts": {
    "build": "react-scripts build && sentry-cli releases files upload-sourcemaps ./build"
  }
}

// .sentryclirc
[defaults]
url=https://sentry.io/
org=your-org
project=your-project

[auth]
token=YOUR_AUTH_TOKEN

// Next.js Sentry config
// next.config.js
const { withSentryConfig } = require('@sentry/nextjs');

module.exports = withSentryConfig(
  {
    // Next.js config
  },
  {
    silent: true,
    org: 'your-org',
    project: 'your-project'
  }
);

// Error monitoring best practices
// 1. Set user context
Sentry.setUser({ id: user.id, email: user.email });

// 2. Add tags for filtering
Sentry.setTag('environment', 'production');
Sentry.setTag('feature', 'checkout');

// 3. Add breadcrumbs
Sentry.addBreadcrumb({
  message: 'Button clicked',
  category: 'ui.click',
  level: 'info'
});

// 4. Filter noise
beforeSend(event) {
  // Ignore known third-party errors
  if (event.exception?.values?.[0]?.value?.includes('third-party-script')) {
    return null;
  }
  return event;
}

// 5. Sample rates
tracesSampleRate: process.env.NODE_ENV === 'production' ? 0.1 : 1.0,

// Custom error class
class ApplicationError extends Error {
  constructor(message, code, context) {
    super(message);
    this.name = 'ApplicationError';
    this.code = code;
    this.context = context;
  }
}

// Report with context
try {
  throw new ApplicationError('Payment failed', 'PAYMENT_ERROR', {
    userId: user.id,
    amount: 99.99
  });
} catch (error) {
  Sentry.captureException(error);
}

Sentry Benefits

  • ✅ Real-time error tracking
  • ✅ Stack traces with source maps
  • ✅ Breadcrumbs (user actions)
  • ✅ Release tracking
  • ✅ Performance monitoring
  • ✅ Alerts & notifications
  • ✅ User context & tags
  • ✅ Free tier: 5K errors/month

LogRocket Benefits

  • ✅ Session replay (video)
  • ✅ Console logs capture
  • ✅ Network requests/responses
  • ✅ Redux state timeline
  • ✅ User interactions
  • ✅ Performance metrics
  • ✅ Integration with Sentry
  • 💰 Paid ($99+/month)
Industry Standard: 85% of top companies use Sentry. 1M+ developers. Free tier covers small projects. Essential for production apps.

3. Try-Catch Async Error Handling

Pattern Use Case Pros Cons
Try-Catch Sync code, async/await Simple, catches synchronous errors Verbose, doesn't catch all async errors
Promise.catch() Promise chains Chainable, handles async rejections Can be verbose with multiple catches
Global handlers Unhandled errors/rejections Catches everything as last resort Should not be primary error handling
Error wrapper Consistent API error handling DRY, centralized logic Requires setup

Example: Async Error Handling Patterns

// Basic try-catch with async/await
async function fetchUserData(userId) {
  try {
    const response = await fetch(`/api/users/${userId}`);
    
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    
    const data = await response.json();
    return data;
  } catch (error) {
    console.error('Failed to fetch user:', error);
    throw error; // Re-throw or handle
  }
}

// Promise chain with catch
function fetchUserData(userId) {
  return fetch(`/api/users/${userId}`)
    .then(response => {
      if (!response.ok) {
        throw new Error(`HTTP ${response.status}`);
      }
      return response.json();
    })
    .catch(error => {
      console.error('Fetch failed:', error);
      throw error;
    });
}

// React component with error handling
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  
  useEffect(() => {
    const fetchUser = async () => {
      try {
        setLoading(true);
        setError(null);
        const data = await fetchUserData(userId);
        setUser(data);
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    };
    
    fetchUser();
  }, [userId]);
  
  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error}</div>;
  return <div>{user.name}</div>;
}

// Custom error classes
class APIError extends Error {
  constructor(message, statusCode, response) {
    super(message);
    this.name = 'APIError';
    this.statusCode = statusCode;
    this.response = response;
  }
}

class NetworkError extends Error {
  constructor(message) {
    super(message);
    this.name = 'NetworkError';
  }
}

class ValidationError extends Error {
  constructor(message, fields) {
    super(message);
    this.name = 'ValidationError';
    this.fields = fields;
  }
}

// API wrapper with error handling
async function apiRequest(url, options = {}) {
  try {
    const response = await fetch(url, {
      ...options,
      headers: {
        'Content-Type': 'application/json',
        ...options.headers
      }
    });
    
    const data = await response.json();
    
    if (!response.ok) {
      throw new APIError(
        data.message || 'API request failed',
        response.status,
        data
      );
    }
    
    return data;
  } catch (error) {
    if (error instanceof APIError) {
      throw error;
    }
    
    if (error.name === 'TypeError' && error.message.includes('fetch')) {
      throw new NetworkError('Network connection failed');
    }
    
    throw error;
  }
}

// Usage with specific error handling
async function login(email, password) {
  try {
    const data = await apiRequest('/api/login', {
      method: 'POST',
      body: JSON.stringify({ email, password })
    });
    return data;
  } catch (error) {
    if (error instanceof APIError) {
      if (error.statusCode === 401) {
        throw new Error('Invalid credentials');
      }
      if (error.statusCode === 429) {
        throw new Error('Too many attempts. Try again later.');
      }
    }
    
    if (error instanceof NetworkError) {
      throw new Error('No internet connection');
    }
    
    throw new Error('Login failed. Please try again.');
  }
}

// React Query error handling
import { useQuery } from '@tanstack/react-query';

function UserProfile({ userId }) {
  const { data, error, isLoading } = useQuery({
    queryKey: ['user', userId],
    queryFn: () => fetchUserData(userId),
    retry: (failureCount, error) => {
      // Don't retry on 404
      if (error.statusCode === 404) return false;
      return failureCount < 3;
    },
    onError: (error) => {
      console.error('Query failed:', error);
      toast.error(error.message);
    }
  });
  
  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;
  return <div>{data.name}</div>;
}

// Axios interceptor for error handling
import axios from 'axios';

const api = axios.create({
  baseURL: '/api'
});

api.interceptors.response.use(
  (response) => response,
  (error) => {
    if (error.response?.status === 401) {
      // Redirect to login
      window.location.href = '/login';
    }
    
    if (error.response?.status === 403) {
      toast.error('Permission denied');
    }
    
    if (error.response?.status >= 500) {
      toast.error('Server error. Please try again.');
    }
    
    return Promise.reject(error);
  }
);

// Global error handlers
window.addEventListener('error', (event) => {
  console.error('Global error:', event.error);
  
  // Report to monitoring service
  Sentry.captureException(event.error);
  
  // Show user-friendly message
  toast.error('Something went wrong');
});

window.addEventListener('unhandledrejection', (event) => {
  console.error('Unhandled promise rejection:', event.reason);
  
  Sentry.captureException(event.reason);
  toast.error('An unexpected error occurred');
});

// Async error wrapper utility
function asyncHandler(fn) {
  return async (...args) => {
    try {
      return await fn(...args);
    } catch (error) {
      console.error('Async handler caught error:', error);
      throw error;
    }
  };
}

// Usage
const safeFunction = asyncHandler(async (userId) => {
  const data = await fetchUserData(userId);
  return data;
});

// Error handling in event handlers
function MyComponent() {
  const handleClick = async () => {
    try {
      await performAction();
      toast.success('Action completed');
    } catch (error) {
      console.error('Action failed:', error);
      toast.error(error.message);
    }
  };
  
  return <button onClick={handleClick}>Click me</button>;
}

// Parallel requests with error handling
async function fetchAllData() {
  try {
    const [users, posts, comments] = await Promise.all([
      fetchUsers(),
      fetchPosts(),
      fetchComments()
    ]);
    
    return { users, posts, comments };
  } catch (error) {
    // If any request fails, all fail
    console.error('One or more requests failed:', error);
    throw error;
  }
}

// Parallel with individual error handling
async function fetchAllDataSafe() {
  const results = await Promise.allSettled([
    fetchUsers(),
    fetchPosts(),
    fetchComments()
  ]);
  
  const data = {
    users: results[0].status === 'fulfilled' ? results[0].value : [],
    posts: results[1].status === 'fulfilled' ? results[1].value : [],
    comments: results[2].status === 'fulfilled' ? results[2].value : []
  };
  
  const errors = results
    .filter(r => r.status === 'rejected')
    .map(r => r.reason);
  
  if (errors.length > 0) {
    console.error('Some requests failed:', errors);
  }
  
  return data;
}

// TypeScript error handling
type Result<T, E = Error> = 
  | { success: true; data: T }
  | { success: false; error: E };

async function safeFetch<T>(url: string): Promise<Result<T>> {
  try {
    const response = await fetch(url);
    const data = await response.json();
    return { success: true, data };
  } catch (error) {
    return { success: false, error: error as Error };
  }
}

// Usage
const result = await safeFetch<User>('/api/user');
if (result.success) {
  console.log(result.data.name);
} else {
  console.error(result.error.message);
}

Error Handling Best Practices

  • ✅ Always handle async errors
  • ✅ Use try-catch with async/await
  • ✅ Create custom error classes
  • ✅ Centralize API error handling
  • ✅ Log errors to monitoring
  • ✅ Show user-friendly messages
  • ✅ Handle network errors separately
  • ✅ Use global handlers as fallback

Common Error Types

Error Cause
TypeError Undefined access, null reference
ReferenceError Variable not defined
SyntaxError Invalid code syntax
NetworkError Fetch failed, offline
APIError HTTP 4xx/5xx responses
Error Recovery: 80% of errors are recoverable with retry logic. Implement exponential backoff for transient failures.

4. Toast Notifications User Feedback

Library Bundle Size Features Use Case
react-hot-toast ~5KB Lightweight, customizable, promise API Modern, minimal, best DX
react-toastify ~15KB Feature-rich, progress bars, auto-close Most popular, full-featured
sonner HOT ~3KB Radix UI based, accessible, beautiful New, modern, best design
notistack ~20KB Material-UI integration, stacking MUI projects, advanced features

Example: Toast Notifications Implementation

// react-hot-toast
npm install react-hot-toast

import toast, { Toaster } from 'react-hot-toast';

function App() {
  return (
    <>
      <Toaster position="top-right" />
      <MyApp />
    </>
  );
}

// Basic usage
const notify = () => toast('Hello World');
const success = () => toast.success('Saved successfully');
const error = () => toast.error('Failed to save');
const loading = () => toast.loading('Loading...');

// Promise-based toast
const saveData = async () => {
  toast.promise(
    api.saveData(),
    {
      loading: 'Saving...',
      success: 'Saved successfully',
      error: 'Failed to save'
    }
  );
};

// Custom duration
toast.success('Saved', { duration: 5000 });

// Custom styling
toast.success('Success', {
  style: {
    background: '#28a745',
    color: '#fff'
  },
  icon: '✅'
});

// Dismissible toast with action
toast((t) => (
  <div>
    <p>Item deleted</p>
    <button onClick={() => {
      undoDelete();
      toast.dismiss(t.id);
    }}>
      Undo
    </button>
  </div>
), {
  duration: 5000
});

// Custom toast component
toast.custom((t) => (
  <div
    className={`custom-toast ${t.visible ? 'animate-enter' : 'animate-leave'}`}
  >
    Custom content
  </div>
));

// Sonner (modern, accessible)
npm install sonner

import { Toaster, toast } from 'sonner';

function App() {
  return (
    <>
      <Toaster position="bottom-right" richColors />
      <MyApp />
    </>
  );
}

// Usage
toast('Event created');
toast.success('Successfully saved');
toast.error('Something went wrong');
toast.warning('Be careful');
toast.info('Update available');

// With description
toast.success('Success', {
  description: 'Your changes have been saved'
});

// With action button
toast('Event created', {
  action: {
    label: 'Undo',
    onClick: () => undoAction()
  }
});

// Promise with loading state
toast.promise(fetchData(), {
  loading: 'Loading...',
  success: (data) => `${data.name} loaded`,
  error: 'Failed to load'
});

// react-toastify
npm install react-toastify

import { ToastContainer, toast } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';

function App() {
  return (
    <>
      <ToastContainer
        position="top-right"
        autoClose={5000}
        hideProgressBar={false}
        newestOnTop
        closeOnClick
        pauseOnHover
      />
      <MyApp />
    </>
  );
}

// Usage
toast.success('Success message');
toast.error('Error message');
toast.warn('Warning message');
toast.info('Info message');

// With custom options
toast.success('Saved', {
  position: 'bottom-center',
  autoClose: 3000,
  hideProgressBar: true
});

// Update existing toast
const toastId = toast.loading('Uploading...');

// Later
toast.update(toastId, {
  render: 'Upload complete',
  type: 'success',
  isLoading: false,
  autoClose: 3000
});

// Custom component
const CustomToast = ({ closeToast, message }) => (
  <div>
    <p>{message}</p>
    <button onClick={closeToast}>Close</button>
  </div>
);

toast(<CustomToast message="Hello" />);

// Toast notification patterns
// Success toast after API call
const handleSave = async () => {
  try {
    await api.saveData(data);
    toast.success('Data saved successfully');
  } catch (error) {
    toast.error('Failed to save data');
  }
};

// Loading state with promise
const handleSubmit = async () => {
  const promise = api.submitForm(formData);
  
  toast.promise(promise, {
    loading: 'Submitting...',
    success: 'Form submitted successfully',
    error: (err) => `Error: ${err.message}`
  });
};

// Undo action toast
const handleDelete = (itemId) => {
  const deletedItem = items.find(i => i.id === itemId);
  
  // Optimistically remove
  setItems(items.filter(i => i.id !== itemId));
  
  // Show undo toast
  toast((t) => (
    <div>
      <span>Item deleted</span>
      <button
        onClick={() => {
          setItems([...items, deletedItem]);
          toast.dismiss(t.id);
        }}
      >
        Undo
      </button>
    </div>
  ), {
    duration: 5000
  });
  
  // Permanently delete after timeout
  setTimeout(() => {
    api.deleteItem(itemId);
  }, 5000);
};

// Multiple toasts with limit
const MAX_TOASTS = 3;

function showToast(message) {
  if (toast.visibleToasts().length >= MAX_TOASTS) {
    return;
  }
  toast(message);
}

// Toast with React Query
import { useMutation } from '@tanstack/react-query';

function MyComponent() {
  const mutation = useMutation({
    mutationFn: saveData,
    onSuccess: () => {
      toast.success('Saved successfully');
    },
    onError: (error) => {
      toast.error(error.message);
    }
  });
  
  return (
    <button onClick={() => mutation.mutate(data)}>
      Save
    </button>
  );
}

// Global toast wrapper
export const notify = {
  success: (message) => toast.success(message),
  error: (message) => toast.error(message),
  info: (message) => toast.info(message),
  warning: (message) => toast.warning(message),
  promise: (promise, messages) => toast.promise(promise, messages)
};

// Usage throughout app
import { notify } from './utils/toast';

notify.success('Operation completed');

// Toast accessibility
<Toaster
  position="top-right"
  toastOptions={{
    role: 'status',
    ariaLive: 'polite'
  }}
/>

// Custom toast with close button
toast.custom((t) => (
  <div role="alert" aria-live="assertive">
    <p>{message}</p>
    <button
      onClick={() => toast.dismiss(t.id)}
      aria-label="Close notification"
    >
      ×
    </button>
  </div>
));

Toast Best Practices

  • ✅ Use for temporary feedback
  • ✅ Auto-dismiss after 3-5 seconds
  • ✅ Success = green, Error = red
  • ✅ Allow manual dismiss
  • ✅ Limit to 3 toasts max
  • ✅ Position: top-right or bottom-center
  • ✅ Add undo for destructive actions
  • ⚠️ Don't use for critical errors

When to Use Toasts

Use Case Type
Form saved Success
Item deleted Success + Undo
API error Error
Network offline Warning
Update available Info
User Experience: Toast notifications provide non-intrusive feedback. Don't overuse - limit to 3 visible at once.

5. Retry Logic Exponential Backoff

Strategy Wait Time Use Case Example
Fixed Delay Same delay between retries Simple, non-critical operations 1s, 1s, 1s
Linear Backoff Linearly increasing delay Moderate load scenarios 1s, 2s, 3s
Exponential Backoff Exponentially increasing delay Rate-limited APIs, best practice 1s, 2s, 4s, 8s
Exponential + Jitter Exponential with randomization Prevent thundering herd, production 1s, 2.3s, 4.7s

Example: Retry Logic Implementation

// Simple retry function
async function retry(fn, maxAttempts = 3, delay = 1000) {
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    try {
      return await fn();
    } catch (error) {
      if (attempt === maxAttempts) {
        throw error;
      }
      
      console.log(`Attempt ${attempt} failed, retrying...`);
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
}

// Usage
const data = await retry(() => fetchData(), 3, 1000);

// Exponential backoff
async function retryWithExponentialBackoff(
  fn,
  maxAttempts = 5,
  baseDelay = 1000,
  maxDelay = 30000
) {
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    try {
      return await fn();
    } catch (error) {
      if (attempt === maxAttempts) {
        throw error;
      }
      
      // Exponential: 1s, 2s, 4s, 8s, 16s
      const delay = Math.min(baseDelay * Math.pow(2, attempt - 1), maxDelay);
      
      console.log(`Attempt ${attempt} failed, waiting ${delay}ms before retry`);
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
}

// Exponential backoff with jitter (prevents thundering herd)
function calculateBackoffWithJitter(attempt, baseDelay = 1000, maxDelay = 30000) {
  const exponentialDelay = Math.min(baseDelay * Math.pow(2, attempt - 1), maxDelay);
  const jitter = Math.random() * exponentialDelay * 0.3; // ±30% jitter
  return exponentialDelay + jitter;
}

async function retryWithJitter(fn, maxAttempts = 5) {
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    try {
      return await fn();
    } catch (error) {
      if (attempt === maxAttempts) {
        throw error;
      }
      
      const delay = calculateBackoffWithJitter(attempt);
      console.log(`Retry in ${Math.round(delay)}ms`);
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
}

// Conditional retry (only for specific errors)
async function retryOnError(fn, shouldRetry, maxAttempts = 3) {
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    try {
      return await fn();
    } catch (error) {
      if (attempt === maxAttempts || !shouldRetry(error)) {
        throw error;
      }
      
      const delay = 1000 * Math.pow(2, attempt - 1);
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
}

// Usage: Only retry on network errors
const data = await retryOnError(
  fetchData,
  (error) => error.name === 'NetworkError' || error.statusCode >= 500,
  3
);

// React Query with retry
import { useQuery } from '@tanstack/react-query';

function MyComponent() {
  const { data, error, isLoading } = useQuery({
    queryKey: ['data'],
    queryFn: fetchData,
    retry: 3,
    retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000)
  });
}

// Axios with retry interceptor
npm install axios-retry

import axios from 'axios';
import axiosRetry from 'axios-retry';

const api = axios.create({
  baseURL: '/api'
});

axiosRetry(api, {
  retries: 3,
  retryDelay: axiosRetry.exponentialDelay,
  retryCondition: (error) => {
    return axiosRetry.isNetworkOrIdempotentRequestError(error) ||
           error.response?.status === 429; // Rate limit
  }
});

// SWR with retry
import useSWR from 'swr';

function Profile() {
  const { data, error } = useSWR('/api/user', fetcher, {
    onErrorRetry: (error, key, config, revalidate, { retryCount }) => {
      // Don't retry on 404
      if (error.status === 404) return;
      
      // Max 5 retries
      if (retryCount >= 5) return;
      
      // Exponential backoff
      setTimeout(() => revalidate({ retryCount }), 1000 * Math.pow(2, retryCount));
    }
  });
}

// Fetch with retry wrapper
async function fetchWithRetry(url, options = {}, maxRetries = 3) {
  const { retry = maxRetries, retryDelay = 1000, ...fetchOptions } = options;
  
  for (let attempt = 0; attempt <= retry; attempt++) {
    try {
      const response = await fetch(url, fetchOptions);
      
      // Retry on 5xx errors
      if (response.status >= 500 && attempt < retry) {
        throw new Error(`Server error: ${response.status}`);
      }
      
      return response;
    } catch (error) {
      if (attempt === retry) {
        throw error;
      }
      
      const delay = retryDelay * Math.pow(2, attempt);
      console.log(`Retry ${attempt + 1}/${retry} after ${delay}ms`);
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
}

// Usage
const response = await fetchWithRetry('/api/data', {
  retry: 5,
  retryDelay: 1000
});

// Retry with abort controller (cancellable)
async function retryWithAbort(fn, signal, maxAttempts = 3) {
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    if (signal?.aborted) {
      throw new Error('Request aborted');
    }
    
    try {
      return await fn(signal);
    } catch (error) {
      if (attempt === maxAttempts || signal?.aborted) {
        throw error;
      }
      
      await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
    }
  }
}

// Usage
const controller = new AbortController();

try {
  const data = await retryWithAbort(
    (signal) => fetch('/api/data', { signal }),
    controller.signal,
    3
  );
} catch (error) {
  console.error('Failed after retries:', error);
}

// Cancel if needed
controller.abort();

// React hook for retry
function useRetry(fn, options = {}) {
  const { maxAttempts = 3, baseDelay = 1000 } = options;
  const [attempt, setAttempt] = useState(0);
  const [isRetrying, setIsRetrying] = useState(false);
  
  const execute = async () => {
    for (let i = 1; i <= maxAttempts; i++) {
      try {
        setAttempt(i);
        setIsRetrying(i > 1);
        
        const result = await fn();
        setIsRetrying(false);
        return result;
      } catch (error) {
        if (i === maxAttempts) {
          setIsRetrying(false);
          throw error;
        }
        
        const delay = baseDelay * Math.pow(2, i - 1);
        await new Promise(resolve => setTimeout(resolve, delay));
      }
    }
  };
  
  return { execute, attempt, isRetrying };
}

// Usage
function MyComponent() {
  const { execute, attempt, isRetrying } = useRetry(fetchData, {
    maxAttempts: 5,
    baseDelay: 1000
  });
  
  const handleClick = async () => {
    try {
      const data = await execute();
      console.log('Success:', data);
    } catch (error) {
      console.error('Failed after retries:', error);
    }
  };
  
  return (
    <div>
      <button onClick={handleClick} disabled={isRetrying}>
        Fetch Data
      </button>
      {isRetrying && <p>Retrying... (Attempt {attempt})</p>}
    </div>
  );
}

// Retry with progress callback
async function retryWithProgress(fn, onProgress, maxAttempts = 3) {
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    try {
      onProgress({ attempt, status: 'trying' });
      const result = await fn();
      onProgress({ attempt, status: 'success' });
      return result;
    } catch (error) {
      onProgress({ attempt, status: 'failed', error });
      
      if (attempt === maxAttempts) {
        throw error;
      }
      
      const delay = 1000 * Math.pow(2, attempt - 1);
      onProgress({ attempt, status: 'waiting', delay });
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
}

Retry Strategy Comparison

Attempt Fixed Exponential
1 1s 1s
2 1s 2s
3 1s 4s
4 1s 8s
5 1s 16s

When to Retry

  • ✅ Network errors (timeout, connection)
  • ✅ 5xx server errors (500-599)
  • ✅ 429 rate limit exceeded
  • ✅ 503 service unavailable
  • ❌ 4xx client errors (except 429)
  • ❌ 401 unauthorized
  • ❌ 404 not found
  • ❌ 400 bad request
AWS Best Practice: Use exponential backoff with jitter to prevent thundering herd. AWS SDKs implement this by default.

6. Circuit Breaker Pattern Frontend

State Behavior Transition Purpose
Closed Normal operation, requests pass through Open after N failures Allow traffic when healthy
Open Fail fast, reject requests immediately Half-Open after timeout Prevent cascading failures
Half-Open Test with limited requests Closed on success, Open on failure Gradual recovery testing

Example: Circuit Breaker Implementation

// Circuit breaker class
class CircuitBreaker {
  constructor(options = {}) {
    this.failureThreshold = options.failureThreshold || 5;
    this.successThreshold = options.successThreshold || 2;
    this.timeout = options.timeout || 60000; // 60s
    
    this.state = 'CLOSED';
    this.failureCount = 0;
    this.successCount = 0;
    this.nextAttempt = Date.now();
  }
  
  async execute(fn) {
    if (this.state === 'OPEN') {
      if (Date.now() < this.nextAttempt) {
        throw new Error('Circuit breaker is OPEN');
      }
      
      // Try half-open
      this.state = 'HALF_OPEN';
    }
    
    try {
      const result = await fn();
      this.onSuccess();
      return result;
    } catch (error) {
      this.onFailure();
      throw error;
    }
  }
  
  onSuccess() {
    this.failureCount = 0;
    
    if (this.state === 'HALF_OPEN') {
      this.successCount++;
      
      if (this.successCount >= this.successThreshold) {
        this.state = 'CLOSED';
        this.successCount = 0;
      }
    }
  }
  
  onFailure() {
    this.failureCount++;
    this.successCount = 0;
    
    if (this.failureCount >= this.failureThreshold) {
      this.state = 'OPEN';
      this.nextAttempt = Date.now() + this.timeout;
    }
  }
  
  getState() {
    return this.state;
  }
  
  reset() {
    this.state = 'CLOSED';
    this.failureCount = 0;
    this.successCount = 0;
  }
}

// Usage
const breaker = new CircuitBreaker({
  failureThreshold: 5,
  successThreshold: 2,
  timeout: 60000
});

async function fetchData() {
  try {
    return await breaker.execute(() => fetch('/api/data'));
  } catch (error) {
    if (error.message === 'Circuit breaker is OPEN') {
      // Use cached data or show error
      return getCachedData();
    }
    throw error;
  }
}

// React hook for circuit breaker
function useCircuitBreaker(options) {
  const breakerRef = useRef(new CircuitBreaker(options));
  const [state, setState] = useState('CLOSED');
  
  const execute = useCallback(async (fn) => {
    try {
      const result = await breakerRef.current.execute(fn);
      setState(breakerRef.current.getState());
      return result;
    } catch (error) {
      setState(breakerRef.current.getState());
      throw error;
    }
  }, []);
  
  const reset = useCallback(() => {
    breakerRef.current.reset();
    setState('CLOSED');
  }, []);
  
  return { execute, state, reset };
}

// Usage in component
function DataFetcher() {
  const { execute, state, reset } = useCircuitBreaker({
    failureThreshold: 3,
    timeout: 30000
  });
  
  const [data, setData] = useState(null);
  const [error, setError] = useState(null);
  
  const fetchData = async () => {
    try {
      const result = await execute(() => api.getData());
      setData(result);
      setError(null);
    } catch (err) {
      setError(err.message);
    }
  };
  
  return (
    <div>
      <p>Circuit State: {state}</p>
      
      {state === 'OPEN' && (
        <div>
          <p>Service unavailable. Using cached data.</p>
          <button onClick={reset}>Reset Circuit</button>
        </div>
      )}
      
      <button onClick={fetchData} disabled={state === 'OPEN'}>
        Fetch Data
      </button>
      
      {error && <p>Error: {error}</p>}
    </div>
  );
}

// Circuit breaker with fallback
class CircuitBreakerWithFallback extends CircuitBreaker {
  constructor(options = {}) {
    super(options);
    this.fallback = options.fallback || (() => null);
  }
  
  async execute(fn) {
    try {
      return await super.execute(fn);
    } catch (error) {
      if (this.state === 'OPEN') {
        return this.fallback();
      }
      throw error;
    }
  }
}

// Usage with fallback
const breaker = new CircuitBreakerWithFallback({
  failureThreshold: 5,
  timeout: 60000,
  fallback: () => getCachedData()
});

// API service with circuit breaker
class APIService {
  constructor() {
    this.breakers = new Map();
  }
  
  getBreaker(endpoint) {
    if (!this.breakers.has(endpoint)) {
      this.breakers.set(endpoint, new CircuitBreaker({
        failureThreshold: 3,
        timeout: 30000
      }));
    }
    return this.breakers.get(endpoint);
  }
  
  async request(endpoint, options) {
    const breaker = this.getBreaker(endpoint);
    
    return breaker.execute(async () => {
      const response = await fetch(endpoint, options);
      
      if (!response.ok) {
        throw new Error(`HTTP ${response.status}`);
      }
      
      return response.json();
    });
  }
}

// Usage
const api = new APIService();

try {
  const data = await api.request('/api/users');
} catch (error) {
  console.error('Request failed:', error);
}

// Circuit breaker with metrics
class CircuitBreakerWithMetrics extends CircuitBreaker {
  constructor(options = {}) {
    super(options);
    this.metrics = {
      totalRequests: 0,
      successfulRequests: 0,
      failedRequests: 0,
      rejectedRequests: 0
    };
  }
  
  async execute(fn) {
    this.metrics.totalRequests++;
    
    if (this.state === 'OPEN' && Date.now() < this.nextAttempt) {
      this.metrics.rejectedRequests++;
      throw new Error('Circuit breaker is OPEN');
    }
    
    try {
      const result = await super.execute(fn);
      this.metrics.successfulRequests++;
      return result;
    } catch (error) {
      this.metrics.failedRequests++;
      throw error;
    }
  }
  
  getMetrics() {
    return {
      ...this.metrics,
      successRate: this.metrics.successfulRequests / this.metrics.totalRequests
    };
  }
}

// Monitoring dashboard
function CircuitBreakerDashboard({ breaker }) {
  const [metrics, setMetrics] = useState(breaker.getMetrics());
  
  useEffect(() => {
    const interval = setInterval(() => {
      setMetrics(breaker.getMetrics());
    }, 1000);
    
    return () => clearInterval(interval);
  }, [breaker]);
  
  return (
    <div>
      <h3>Circuit Breaker Status</h3>
      <p>State: {breaker.getState()}</p>
      <p>Total Requests: {metrics.totalRequests}</p>
      <p>Success Rate: {(metrics.successRate * 100).toFixed(2)}%</p>
      <p>Rejected: {metrics.rejectedRequests}</p>
    </div>
  );
}

// Circuit breaker pattern with multiple services
const breakers = {
  userService: new CircuitBreaker({ failureThreshold: 3 }),
  paymentService: new CircuitBreaker({ failureThreshold: 5 }),
  notificationService: new CircuitBreaker({ failureThreshold: 2 })
};

async function fetchUser(id) {
  return breakers.userService.execute(() => api.getUser(id));
}

async function processPayment(data) {
  return breakers.paymentService.execute(() => api.processPayment(data));
}

// Global circuit breaker state management
const CircuitBreakerContext = createContext();

function CircuitBreakerProvider({ children }) {
  const [breakers] = useState(() => ({
    api: new CircuitBreakerWithMetrics({ failureThreshold: 5 })
  }));
  
  return (
    <CircuitBreakerContext.Provider value={breakers}>
      {children}
    </CircuitBreakerContext.Provider>
  );
}

function useCircuitBreakerAPI() {
  const breakers = useContext(CircuitBreakerContext);
  return breakers.api;
}

Circuit Breaker States

State Action
Closed Allow all requests
Open Reject all requests
Half-Open Test with limited requests

Circuit Breaker Benefits

  • ✅ Prevent cascading failures
  • ✅ Fail fast instead of waiting
  • ✅ Give services time to recover
  • ✅ Improve user experience
  • ✅ Reduce server load
  • ✅ Enable graceful degradation

Error Handling & Resilience Summary

  • Error Boundaries: Catch React render errors. Use react-error-boundary library. Granular boundaries per feature. Don't catch async/event errors
  • Monitoring: Sentry for error tracking (5K free/month). LogRocket for session replay. Source maps for production debugging
  • Try-Catch: Wrap async/await, create custom error classes, centralize API errors, use global handlers as fallback
  • Toast Notifications: react-hot-toast (5KB) or Sonner (3KB). Auto-dismiss 3-5s. Limit to 3 toasts. Add undo for destructive actions
  • Retry Logic: Exponential backoff with jitter (1s, 2s, 4s, 8s). Retry 5xx errors, network failures, 429 rate limits. Don't retry 4xx
  • Circuit Breaker: Fail fast when service down. 3 states: Closed→Open→Half-Open. Prevent cascading failures, enable fallbacks
Production Ready: Implement all 6 patterns for production apps. 99.9% uptime requires resilience. Users expect 200ms response or fallback.