avatar

Le Do Nghiem

Software Engineer

  • About me
  • Books
  • Snippets
  • Blog

© 2026 Le Do Nghiem. All rights reserved.

Contact |

Back to Snippets

useFetch Hook

A custom React hook for fetching data with loading and error states.

LanguageTypeScript
Last UpdatedApr 30, 2025
use-fetch.ts
import { useState, useEffect } from 'react';

interface UseFetchResult<T> {
  data: T | null;
  loading: boolean;
  error: Error | null;
  refetch: () => void;
}

export function useFetch<T>(
  url: string,
  options?: RequestInit
): UseFetchResult<T> {
  const [data, setData] = useState<T | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<Error | null>(null);

  const fetchData = async () => {
    try {
      setLoading(true);
      setError(null);
      const response = await fetch(url, options);
      
      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
      }
      
      const result = await response.json();
      setData(result);
    } catch (err) {
      setError(err instanceof Error ? err : new Error('Unknown error'));
    } finally {
      setLoading(false);
    }
  };

  useEffect(() => {
    fetchData();
  }, [url]);

  return { data, loading, error, refetch: fetchData };
}

Fetching data is a core part of most React applications. This hook encapsulates the common pattern of loading, error handling, and data management.

import { useState, useEffect } from 'react';

interface UseFetchResult<T> {
  data: T | null;
  loading: boolean;
  error: Error | null;
  refetch: () => void;
}

export function useFetch<T>(
  url: string,
  options?: RequestInit
): UseFetchResult<T> {
  const [data, setData] = useState<T | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<Error | null>(null);

  const fetchData = async () => {
    try {
      setLoading(true);
      setError(null);
      const response = await fetch(url, options);
      
      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
      }
      
      const result = await response.json();
      setData(result);
    } catch (err) {
      setError(err instanceof Error ? err : new Error('Unknown error'));
    } finally {
      setLoading(false);
    }
  };

  useEffect(() => {
    fetchData();
  }, [url]);

  return { data, loading, error, refetch: fetchData };
}

How It Works

  • Manages three states: data, loading, and error.
  • Automatically fetches when the URL changes.
  • Provides a refetch function to manually trigger a new request.
  • Handles HTTP errors and network failures.

Example Usage

function UserProfile({ userId }: { userId: string }) {
  const { data, loading, error, refetch } = useFetch<User>(
    `/api/users/${userId}`
  );

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

  return (
    <div>
      <h1>{data.name}</h1>
      <button onClick={refetch}>Refresh</button>
    </div>
  );
}

With POST Request

function CreatePost() {
  const { data, loading, error, refetch } = useFetch<Post>(
    '/api/posts',
    {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ title: 'New Post' }),
    }
  );
}

Use Cases

  • Fetching user data
  • Loading blog posts
  • Retrieving API responses
  • Fetching configuration
  • Loading dashboard data

Notes

  • Automatically refetches when the URL changes.
  • Consider adding request cancellation for cleanup.
  • For more advanced features (caching, retries), consider libraries like react-query or swr.
  • The refetch function allows manual refresh without changing the URL.

A simple, reusable hook that handles the common data fetching pattern with built-in loading and error states.

Previous Snippet

truncateString Function

Next Snippet

useMediaQuery Hook