AdSense Leaderboard

Google AdSense Placeholder

HEADER SLOT

Error Handling Patterns in React: HTTP & Network Errors

Last updated:
Error Handling Patterns: HTTP & Network Errors in React (2025)

Master HTTP and network error handling in React. Learn fetch error patterns, Error Boundaries, custom hooks, retry logic, and production-ready error recovery strategies.

# Error Handling Patterns in React: HTTP & Network Errors

Every production React application encounters errors—network timeouts, API failures, invalid responses, authentication issues. How you handle these errors determines whether users see a working application or a broken one. This comprehensive guide covers everything from basic HTTP error detection to production-ready recovery patterns used in applications handling millions of users.

# Table of Contents

  1. Understanding HTTP vs Network Errors
  2. Basic HTTP Error Detection
  3. Network Error Handling
  4. Error Boundaries in React
  5. Custom Error Handling Hooks
  6. Retry Logic and Exponential Backoff
  7. Real-World Error Patterns
  8. Error Recovery Strategies
  9. FAQ

# Understanding HTTP vs Network Errors

Most developers conflate "HTTP errors" and "network errors," but they're distinct problems requiring different solutions.

# HTTP Errors (4xx, 5xx)

HTTP errors are valid responses from the server indicating something went wrong:

  • 4xx: Client error (bad request, not found, unauthorized)
  • 5xx: Server error (internal server error, service unavailable)
typescript
// HTTP response with error status
const response = await fetch('/api/users/invalid-id');
// Returns 404 (valid HTTP response, but indicates an error)
console.log(response.status); // 404
console.log(response.ok); // false

# Network Errors

Network errors happen when the request never reaches the server:

  • DNS resolution failures
  • Network timeout
  • Connection refused
  • CORS blocked
  • User went offline
typescript
// Network error (request never completes)
try {
  const response = await fetch('https://unreachable-server.invalid');
} catch (error) {
  // Error thrown because network request failed entirely
  console.log(error.message); // "Failed to fetch"
}

# Key Difference

Type What Happens Response Handling
HTTP Error Server responds with error status Valid response object Check response.ok
Network Error Request fails before response Never completes Thrown as exception

# Basic HTTP Error Detection

The fetch API doesn't throw errors for HTTP error status codes. You must check them manually.

# The fetch Promise Gotcha

TypeScript Version

typescript
// ❌ WRONG: fetch doesn't throw on 404/500
async function getUser(id: string) {
  try {
    const response = await fetch(`/api/users/${id}`);
    const user = await response.json();
    return user;
  } catch (error) {
    // This only catches network errors, NOT HTTP 404/500!
    console.log('Error:', error);
  }
}

// ✅ CORRECT: Check response.ok
async function getUser(id: string) {
  try {
    const response = await fetch(`/api/users/${id}`);
    
    // Check status code before processing response
    if (!response.ok) {
      const errorData = await response.json();
      throw new Error(
        `API Error ${response.status}: ${errorData.message || response.statusText}`
      );
    }
    
    const user = await response.json();
    return user;
  } catch (error) {
    // Now this catches BOTH HTTP errors and network errors
    console.log('Error:', error instanceof Error ? error.message : String(error));
    throw error;
  }
}

JavaScript Version

javascript
// ❌ WRONG
async function getUser(id) {
  try {
    const response = await fetch(`/api/users/${id}`);
    const user = await response.json();
    return user;
  } catch (error) {
    console.log('Error:', error);
  }
}

// ✅ CORRECT
async function getUser(id) {
  try {
    const response = await fetch(`/api/users/${id}`);
    
    if (!response.ok) {
      const errorData = await response.json();
      throw new Error(
        `API Error ${response.status}: ${errorData.message || response.statusText}`
      );
    }
    
    const user = await response.json();
    return user;
  } catch (error) {
    console.log('Error:', error instanceof Error ? error.message : String(error));
    throw error;
  }
}

# Handling Different HTTP Status Codes

Not all HTTP errors should be handled the same way:

typescript
interface ErrorResponse {
  status: number;
  message: string;
  retryable: boolean;
}

async function handleHttpError(response: Response): Promise<ErrorResponse> {
  const body = await response.json().catch(() => ({}));

  // Client errors (4xx) - usually not retryable
  if (response.status >= 400 && response.status < 500) {
    return {
      status: response.status,
      message: body.message || response.statusText,
      retryable: false, // User should fix the request
    };
  }

  // Server errors (5xx) - usually retryable
  if (response.status >= 500) {
    return {
      status: response.status,
      message: body.message || 'Server error',
      retryable: true, // Retry after delay
    };
  }

  return {
    status: response.status,
    message: response.statusText,
    retryable: false,
  };
}

// Usage
async function fetchData(url: string) {
  const response = await fetch(url);

  if (!response.ok) {
    const error = await handleHttpError(response);
    
    if (error.retryable) {
      console.log('Server error, can retry later');
    } else {
      console.log('Client error, user must fix request');
    }
    
    throw new Error(error.message);
  }

  return response.json();
}

# Network Error Handling

Network errors are thrown as exceptions and require try-catch handling.

# Common Network Errors

TypeScript Version

typescript
import { useEffect, useState } from 'react';

interface NetworkError {
  type: 'timeout' | 'offline' | 'network' | 'unknown';
  message: string;
  originalError: Error;
}

function classifyNetworkError(error: unknown): NetworkError {
  if (!(error instanceof Error)) {
    return {
      type: 'unknown',
      message: String(error),
      originalError: new Error(String(error)),
    };
  }

  const message = error.message.toLowerCase();

  // Timeout detection
  if (message.includes('timeout') || message.includes('abort')) {
    return {
      type: 'timeout',
      message: 'Request took too long. Please check your connection.',
      originalError: error,
    };
  }

  // Offline detection
  if (message.includes('failed to fetch') && !navigator.onLine) {
    return {
      type: 'offline',
      message: 'You appear to be offline. Check your internet connection.',
      originalError: error,
    };
  }

  // Generic network error
  if (message.includes('failed to fetch') || message.includes('network')) {
    return {
      type: 'network',
      message: 'Network error occurred. Please try again.',
      originalError: error,
    };
  }

  return {
    type: 'unknown',
    message: error.message,
    originalError: error,
  };
}

function useData(url: string) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<NetworkError | null>(null);

  useEffect(() => {
    let isMounted = true;

    const fetchData = async () => {
      try {
        setLoading(true);
        setError(null);

        const response = await fetch(url, { signal: AbortSignal.timeout(5000) });

        if (!response.ok) {
          throw new Error(`HTTP ${response.status}: ${response.statusText}`);
        }

        const result = await response.json();
        
        if (isMounted) {
          setData(result);
        }
      } catch (err) {
        if (isMounted) {
          const networkError = classifyNetworkError(err);
          setError(networkError);
        }
      } finally {
        if (isMounted) {
          setLoading(false);
        }
      }
    };

    fetchData();

    return () => {
      isMounted = false;
    };
  }, [url]);

  return { data, loading, error };
}

JavaScript Version

javascript
function classifyNetworkError(error) {
  const message = error.message.toLowerCase();

  if (message.includes('timeout') || message.includes('abort')) {
    return {
      type: 'timeout',
      message: 'Request took too long. Please check your connection.',
      originalError: error,
    };
  }

  if (message.includes('failed to fetch') && !navigator.onLine) {
    return {
      type: 'offline',
      message: 'You appear to be offline. Check your internet connection.',
      originalError: error,
    };
  }

  if (message.includes('failed to fetch') || message.includes('network')) {
    return {
      type: 'network',
      message: 'Network error occurred. Please try again.',
      originalError: error,
    };
  }

  return {
    type: 'unknown',
    message: error.message,
    originalError: error,
  };
}

function useData(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    let isMounted = true;

    const fetchData = async () => {
      try {
        setLoading(true);
        setError(null);

        const response = await fetch(url, { signal: AbortSignal.timeout(5000) });

        if (!response.ok) {
          throw new Error(`HTTP ${response.status}: ${response.statusText}`);
        }

        const result = await response.json();
        if (isMounted) setData(result);
      } catch (err) {
        if (isMounted) {
          const networkError = classifyNetworkError(err);
          setError(networkError);
        }
      } finally {
        if (isMounted) setLoading(false);
      }
    };

    fetchData();

    return () => {
      isMounted = false;
    };
  }, [url]);

  return { data, loading, error };
}

# Error Boundaries in React

Error Boundaries catch rendering errors in child components. They don't catch async errors from fetch.

# Creating an Error Boundary

TypeScript Version

typescript
import { Component, ReactNode } from 'react';

interface ErrorBoundaryProps {
  children: ReactNode;
  fallback?: (error: Error) => ReactNode;
}

interface ErrorBoundaryState {
  error: Error | null;
  hasError: boolean;
}

class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
  constructor(props: ErrorBoundaryProps) {
    super(props);
    this.state = { error: null, hasError: false };
  }

  static getDerivedStateFromError(error: Error): ErrorBoundaryState {
    return { hasError: true, error };
  }

  componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
    // Log error to service (e.g., Sentry)
    console.error('Error caught by boundary:', error, errorInfo);
  }

  render() {
    if (this.state.hasError && this.state.error) {
      return (
        this.props.fallback?.(this.state.error) || (
          <div className="error-boundary">
            <h2>Something went wrong</h2>
            <p>{this.state.error.message}</p>
            <button onClick={() => this.setState({ hasError: false, error: null })}>
              Try again
            </button>
          </div>
        )
      );
    }

    return this.props.children;
  }
}

export default ErrorBoundary;

// Usage
function App() {
  return (
    <ErrorBoundary
      fallback={(error) => (
        <div className="error-page">
          <h1>Application Error</h1>
          <p>We encountered an unexpected error: {error.message}</p>
        </div>
      )}
    >
      <YourApp />
    </ErrorBoundary>
  );
}

JavaScript Version

javascript
import { Component } from 'react';

class ErrorBoundary extends Component {
  constructor(props) {
    super(props);
    this.state = { error: null, hasError: false };
  }

  static getDerivedStateFromError(error) {
    return { hasError: true, error };
  }

  componentDidCatch(error, errorInfo) {
    console.error('Error caught by boundary:', error, errorInfo);
  }

  render() {
    if (this.state.hasError && this.state.error) {
      return (
        this.props.fallback?.(this.state.error) || (
          <div className="error-boundary">
            <h2>Something went wrong</h2>
            <p>{this.state.error.message}</p>
            <button onClick={() => this.setState({ hasError: false, error: null })}>
              Try again
            </button>
          </div>
        )
      );
    }

    return this.props.children;
  }
}

export default ErrorBoundary;

# Custom Error Handling Hooks

Create reusable hooks that encapsulate error handling logic.

# useAsync Hook with Error Handling

TypeScript Version

typescript
import { useEffect, useState, useCallback } from 'react';

interface UseAsyncState<T> {
  data: T | null;
  loading: boolean;
  error: Error | null;
}

interface UseAsyncOptions {
  onError?: (error: Error) => void;
  retryCount?: number;
  retryDelay?: number;
}

function useAsync<T>(
  asyncFunction: () => Promise<T>,
  dependencies: React.DependencyList = [],
  options: UseAsyncOptions = {}
): UseAsyncState<T> & { retry: () => void } {
  const { onError, retryCount = 0, retryDelay = 1000 } = options;

  const [state, setState] = useState<UseAsyncState<T>>({
    data: null,
    loading: true,
    error: null,
  });

  const [retries, setRetries] = useState(0);

  const execute = useCallback(async () => {
    try {
      setState({ data: null, loading: true, error: null });
      const result = await asyncFunction();
      setState({ data: result, loading: false, error: null });
      setRetries(0);
    } catch (error) {
      const err = error instanceof Error ? error : new Error(String(error));
      setState({ data: null, loading: false, error: err });

      if (onError) {
        onError(err);
      }

      // Auto-retry logic
      if (retries < retryCount) {
        setTimeout(() => {
          setRetries((prev) => prev + 1);
        }, retryDelay);
      }
    }
  }, [asyncFunction, onError, retryCount, retryDelay, retries]);

  const retry = useCallback(() => {
    setRetries(0);
    execute();
  }, [execute]);

  useEffect(() => {
    execute();
  }, [...dependencies, execute]);

  return { ...state, retry };
}

// Usage
function UserProfile({ userId }: { userId: string }) {
  const { data: user, loading, error, retry } = useAsync(
    () => fetch(`/api/users/${userId}`).then((res) => res.json()),
    [userId],
    {
      retryCount: 3,
      retryDelay: 2000,
      onError: (error) => {
        console.log('Failed to load user:', error.message);
      },
    }
  );

  if (loading) return <div>Loading...</div>;
  if (error) {
    return (
      <div>
        <p>Error: {error.message}</p>
        <button onClick={retry}>Retry</button>
      </div>
    );
  }

  return <div>{user?.name}</div>;
}

JavaScript Version

javascript
function useAsync(asyncFunction, dependencies = [], options = {}) {
  const { onError, retryCount = 0, retryDelay = 1000 } = options;

  const [state, setState] = useState({
    data: null,
    loading: true,
    error: null,
  });

  const [retries, setRetries] = useState(0);

  const execute = useCallback(async () => {
    try {
      setState({ data: null, loading: true, error: null });
      const result = await asyncFunction();
      setState({ data: result, loading: false, error: null });
      setRetries(0);
    } catch (error) {
      const err = error instanceof Error ? error : new Error(String(error));
      setState({ data: null, loading: false, error: err });

      if (onError) onError(err);

      if (retries < retryCount) {
        setTimeout(() => {
          setRetries((prev) => prev + 1);
        }, retryDelay);
      }
    }
  }, [asyncFunction, onError, retryCount, retryDelay, retries]);

  const retry = useCallback(() => {
    setRetries(0);
    execute();
  }, [execute]);

  useEffect(() => {
    execute();
  }, [...dependencies, execute]);

  return { ...state, retry };
}

# Retry Logic and Exponential Backoff

Automatic retries with exponential backoff dramatically improve reliability for transient failures.

TypeScript Version

typescript
interface RetryOptions {
  maxRetries: number;
  initialDelay: number;
  maxDelay: number;
  backoffMultiplier: number;
  isRetryable?: (error: Error, attempt: number) => boolean;
}

async function fetchWithRetry<T>(
  url: string,
  options: Partial<RetryOptions> = {}
): Promise<T> {
  const {
    maxRetries = 3,
    initialDelay = 1000,
    maxDelay = 10000,
    backoffMultiplier = 2,
    isRetryable = (error, attempt) => attempt < maxRetries,
  } = options;

  let lastError: Error | null = null;

  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      const response = await fetch(url);

      if (!response.ok) {
        const error = new Error(
          `HTTP ${response.status}: ${response.statusText}`
        );
        throw error;
      }

      return await response.json();
    } catch (error) {
      lastError = error instanceof Error ? error : new Error(String(error));

      // Don't retry if error isn't retryable
      if (!isRetryable(lastError, attempt)) {
        throw lastError;
      }

      // Don't wait after last attempt
      if (attempt < maxRetries) {
        // Calculate exponential backoff delay
        const delay = Math.min(
          initialDelay * Math.pow(backoffMultiplier, attempt),
          maxDelay
        );

        // Add jitter to prevent thundering herd
        const jitter = Math.random() * delay * 0.1;
        const totalDelay = delay + jitter;

        console.log(
          `Attempt ${attempt + 1} failed, retrying in ${totalDelay.toFixed(0)}ms`
        );

        await new Promise((resolve) => setTimeout(resolve, totalDelay));
      }
    }
  }

  throw lastError || new Error('Failed after retries');
}

// Usage
async function loadData() {
  try {
    const data = await fetchWithRetry('/api/data', {
      maxRetries: 3,
      initialDelay: 1000,
      isRetryable: (error, attempt) => {
        // Don't retry 404s
        if (error.message.includes('404')) return false;
        return attempt < 3;
      },
    });

    console.log('Data loaded:', data);
  } catch (error) {
    console.error('Failed to load data:', error);
  }
}

JavaScript Version

javascript
async function fetchWithRetry(url, options = {}) {
  const {
    maxRetries = 3,
    initialDelay = 1000,
    maxDelay = 10000,
    backoffMultiplier = 2,
    isRetryable = (error, attempt) => attempt < maxRetries,
  } = options;

  let lastError = null;

  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      const response = await fetch(url);

      if (!response.ok) {
        throw new Error(`HTTP ${response.status}: ${response.statusText}`);
      }

      return await response.json();
    } catch (error) {
      lastError = error;

      if (!isRetryable(error, attempt)) {
        throw error;
      }

      if (attempt < maxRetries) {
        const delay = Math.min(
          initialDelay * Math.pow(backoffMultiplier, attempt),
          maxDelay
        );
        const jitter = Math.random() * delay * 0.1;
        const totalDelay = delay + jitter;

        await new Promise((resolve) => setTimeout(resolve, totalDelay));
      }
    }
  }

  throw lastError;
}

# Real-World Error Patterns

# Pattern 1: Form Submission with Error Handling

TypeScript Version

typescript
import { FormEvent, useState } from 'react';

interface SubmitError {
  field?: string;
  message: string;
  code?: string;
}

function LoginForm() {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [loading, setLoading] = useState(false);
  const [errors, setErrors] = useState<SubmitError[]>([]);

  const handleSubmit = async (e: FormEvent) => {
    e.preventDefault();
    setLoading(true);
    setErrors([]);

    try {
      const response = await fetch('/api/login', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ email, password }),
      });

      if (!response.ok) {
        const errorData = await response.json();

        // Handle field-specific errors
        if (response.status === 422) {
          setErrors(
            errorData.errors.map((err: any) => ({
              field: err.field,
              message: err.message,
            }))
          );
          return;
        }

        // Handle generic errors
        throw new Error(errorData.message || 'Login failed');
      }

      const data = await response.json();
      localStorage.setItem('token', data.token);
      window.location.href = '/dashboard';
    } catch (error) {
      const message =
        error instanceof Error ? error.message : 'An error occurred';
      setErrors([{ message }]);
    } finally {
      setLoading(false);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      {errors.length > 0 && (
        <div className="alert alert-error">
          {errors.map((err, i) => (
            <p key={i}>{err.message}</p>
          ))}
        </div>
      )}

      <input
        type="email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        placeholder="Email"
      />

      <input
        type="password"
        value={password}
        onChange={(e) => setPassword(e.target.value)}
        placeholder="Password"
      />

      <button type="submit" disabled={loading}>
        {loading ? 'Logging in...' : 'Login'}
      </button>
    </form>
  );
}

# Pattern 2: Data Loading with Offline Support

TypeScript Version

typescript
import { useEffect, useState } from 'react';

interface CachedData<T> {
  data: T;
  timestamp: number;
}

function useDataWithCache<T>(
  url: string,
  cacheKey: string,
  cacheDuration: number = 5 * 60 * 1000 // 5 minutes
) {
  const [data, setData] = useState<T | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<Error | null>(null);
  const [isOffline, setIsOffline] = useState(!navigator.onLine);

  useEffect(() => {
    const handleOnline = () => setIsOffline(false);
    const handleOffline = () => setIsOffline(true);

    window.addEventListener('online', handleOnline);
    window.addEventListener('offline', handleOffline);

    return () => {
      window.removeEventListener('online', handleOnline);
      window.removeEventListener('offline', handleOffline);
    };
  }, []);

  useEffect(() => {
    let isMounted = true;

    const loadData = async () => {
      try {
        // Check cache first
        const cached = localStorage.getItem(cacheKey);
        if (cached) {
          const { data: cachedData, timestamp } = JSON.parse(
            cached
          ) as CachedData<T>;

          if (Date.now() - timestamp < cacheDuration) {
            if (isMounted) {
              setData(cachedData);
              setLoading(false);
            }
            return;
          }
        }

        // Skip network request if offline
        if (isOffline && cached) {
          if (isMounted) setLoading(false);
          return;
        }

        // Fetch fresh data
        setLoading(true);
        const response = await fetch(url);

        if (!response.ok) {
          throw new Error(`HTTP ${response.status}`);
        }

        const result = await response.json();

        if (isMounted) {
          setData(result);
          setError(null);

          // Cache the data
          localStorage.setItem(
            cacheKey,
            JSON.stringify({ data: result, timestamp: Date.now() })
          );
        }
      } catch (err) {
        if (isMounted) {
          const message =
            err instanceof Error ? err.message : 'Failed to load data';
          setError(new Error(message));

          // Try to use stale cache as fallback
          const cached = localStorage.getItem(cacheKey);
          if (cached) {
            const { data: cachedData } = JSON.parse(cached) as CachedData<T>;
            setData(cachedData);
          }
        }
      } finally {
        if (isMounted) setLoading(false);
      }
    };

    loadData();

    return () => {
      isMounted = false;
    };
  }, [url, cacheKey, cacheDuration, isOffline]);

  return { data, loading, error, isOffline };
}

// Usage
function DataDisplay() {
  const { data, loading, error, isOffline } = useDataWithCache(
    '/api/data',
    'data-cache',
    5 * 60 * 1000
  );

  if (isOffline) {
    return <p>You are offline. Showing cached data.</p>;
  }

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return <div>{JSON.stringify(data)}</div>;
}

# Error Recovery Strategies

# Graceful Degradation

Show what you can when parts of your application fail:

typescript
function Dashboard() {
  const { data: users } = useData('/api/users');
  const { data: analytics } = useData('/api/analytics');
  const { data: posts } = useData('/api/posts');

  return (
    <div>
      <h1>Dashboard</h1>

      {/* Show what loaded, hide what failed */}
      {users && (
        <section>
          <h2>Users</h2>
          <UserList users={users} />
        </section>
      )}

      {analytics && (
        <section>
          <h2>Analytics</h2>
          <AnalyticsChart data={analytics} />
        </section>
      )}

      {posts ? (
        <section>
          <h2>Posts</h2>
          <PostList posts={posts} />
        </section>
      ) : (
        <section className="disabled">
          <h2>Posts</h2>
          <p>Posts service temporarily unavailable</p>
        </section>
      )}
    </div>
  );
}

# Fallback UI

Provide safe defaults when data can't load:

typescript
interface User {
  id: string;
  name: string;
  avatar?: string;
}

function UserCard({ userId }: { userId: string }) {
  const { data: user, error } = useAsync(() =>
    fetch(`/api/users/${userId}`).then((r) => r.json())
  );

  // Fallback UI with skeleton data
  const displayUser: User = user || {
    id: userId,
    name: 'User',
    avatar: undefined,
  };

  return (
    <div className={error ? 'card error' : 'card'}>
      <img
        src={displayUser.avatar || '/default-avatar.png'}
        alt="User avatar"
      />
      <h3>{displayUser.name}</h3>
      {error && <p className="error-hint">Failed to load full details</p>}
    </div>
  );
}

# FAQ

# Q: Why doesn't fetch throw on HTTP errors?

A: The fetch specification considers HTTP errors (4xx, 5xx) as valid responses—the network request succeeded. Only network-level failures throw exceptions. This design requires you to explicitly check response.ok for HTTP errors, which is actually safer than auto-throwing.

# Q: Should I retry all failed requests?

A: No. Only retry transient errors (network timeouts, 5xx errors). Don't retry client errors (400, 401, 404) since retrying won't help. Check the error type before deciding to retry.

# Q: How do I handle timeouts?

A: Use AbortSignal.timeout() (modern) or AbortController:

typescript
// Modern (Chrome 120+)
const response = await fetch(url, { signal: AbortSignal.timeout(5000) });

// Fallback
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 5000);
try {
  const response = await fetch(url, { signal: controller.signal });
} finally {
  clearTimeout(timeoutId);
}

# Q: When should I use Error Boundaries?

A: Error Boundaries catch rendering errors in components, not async fetch errors. Use them to prevent the entire app from crashing when a component breaks. Async errors from fetch need try-catch or promise .catch() handling.

# Q: How do I prevent memory leaks when canceling requests?

A: Always use the cleanup function in useEffect to track whether the component is mounted:

typescript
useEffect(() => {
  let isMounted = true;

  fetch(url).then((data) => {
    if (isMounted) {
      setState(data);
    }
  });

  return () => {
    isMounted = false;
  };
}, [url]);

# Q: Should I show errors to users?

A: Yes, but tactfully. Don't show technical errors like "TypeError: Cannot read properties of undefined". Instead, show user-friendly messages and log technical details for debugging.


Questions? Share your error handling patterns and what production issues you've solved in the comments. How do you balance showing errors to users with maintaining a good user experience?

Sponsored Content

Google AdSense Placeholder

CONTENT SLOT

Sponsored

Google AdSense Placeholder

FOOTER SLOT