← Back to all hooks

useFetch

A powerful hook for fetching data with built-in loading, error states, and request cancellation.

Live Demo

Try changing the user or post ID to see the hook automatically fetch new data. Toggle the skip option to prevent fetching.

👤 Fetch User Data

⏳ Loading...

📝 Fetch Post Data (with skip)

⏳ Loading...

📤 POST Request Example

Fill in the form and click Create Post

🛑 Request Abort Example

Start a slow fetch (5 second delay) and abort it before completion. The hook automatically cancels requests when unmounting or when a new request starts.

Click "Start Fetch" to begin

💡 Features Demonstrated:

  • GET requests with automatic refetching on URL changes
  • POST requests with custom headers and body
  • Request abortion and cancellation
  • Loading, error, and data states
  • Skip option to prevent automatic fetching
  • Manual refetch capability

Code Examples

Basic Usage

import { useFetch } from "@uiblock/hooks";

interface User {
  id: number;
  name: string;
  email: string;
}

export default function UserProfile({ userId }: { userId: string }) {
  const { data, loading, error } = useFetch<User>(
    `https://api.example.com/users/${userId}`
  );

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

  return (
    <div>
      <h2>{data.name}</h2>
      <p>{data.email}</p>
    </div>
  );
}

With Custom Options

import { useFetch } from "@uiblock/hooks";

export default function PostsList() {
  const { data, loading, error, refetch } = useFetch(
    'https://api.example.com/posts',
    {
      method: 'GET',
      headers: {
        'Authorization': 'Bearer token123',
        'Content-Type': 'application/json'
      },
      onSuccess: (data) => {
        console.log('Data fetched successfully:', data);
      },
      onError: (error) => {
        console.error('Fetch failed:', error);
      }
    }
  );

  return (
    <div>
      <button onClick={refetch}>Refresh</button>
      {loading && <p>Loading...</p>}
      {error && <p>Error: {error.message}</p>}
      {data && <pre>{JSON.stringify(data, null, 2)}</pre>}
    </div>
  );
}

Skip Fetching

import { useFetch } from "@uiblock/hooks";
import { useState } from "react";

export default function ConditionalFetch() {
  const [shouldFetch, setShouldFetch] = useState(false);

  const { data, loading } = useFetch(
    'https://api.example.com/data',
    { skip: !shouldFetch }
  );

  return (
    <div>
      <button onClick={() => setShouldFetch(true)}>
        Load Data
      </button>
      {loading && <p>Loading...</p>}
      {data && <pre>{JSON.stringify(data, null, 2)}</pre>}
    </div>
  );
}

Manual Refetch

import { useFetch } from "@uiblock/hooks";

export default function RefetchExample() {
  const { data, loading, refetch } = useFetch(
    'https://api.example.com/random'
  );

  return (
    <div>
      <button onClick={refetch} disabled={loading}>
        {loading ? 'Loading...' : 'Get New Random Data'}
      </button>
      {data && <div>{JSON.stringify(data)}</div>}
    </div>
  );
}

How It Works

Here's the implementation:

import { useState, useEffect, useCallback, useRef } from 'react'

export interface UseFetchOptions extends RequestInit {
  skip?: boolean
  onSuccess?: (data: any) => void
  onError?: (error: Error) => void
}

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

export function useFetch<T = any>(
  url: string,
  options: UseFetchOptions = {}
): UseFetchResult<T> {
  const [data, setData] = useState<T | null>(null)
  const [error, setError] = useState<Error | null>(null)
  const [loading, setLoading] = useState<boolean>(!options.skip)

  const { skip, onSuccess, onError, ...fetchOptions } = options
  const abortControllerRef = useRef<AbortController | null>(null)

  const fetchData = useCallback(async () => {
    if (skip) return

    // Cancel previous request
    if (abortControllerRef.current) {
      abortControllerRef.current.abort()
    }

    abortControllerRef.current = new AbortController()

    setLoading(true)
    setError(null)

    try {
      const response = await fetch(url, {
        ...fetchOptions,
        signal: abortControllerRef.current.signal
      })

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

      const result = await response.json()
      setData(result)
      onSuccess?.(result)
    } catch (err) {
      if (err instanceof Error && err.name === 'AbortError') {
        return
      }
      const error = err instanceof Error ? err : new Error('An error occurred')
      setError(error)
      onError?.(error)
    } finally {
      setLoading(false)
    }
  }, [url, skip, onSuccess, onError])

  useEffect(() => {
    fetchData()

    return () => {
      if (abortControllerRef.current) {
        abortControllerRef.current.abort()
      }
    }
  }, [fetchData])

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

API Reference

Parameters

url: string

The URL to fetch data from

options?: UseFetchOptions

Optional configuration object

  • skip: Skip automatic fetching
  • onSuccess: Callback on successful fetch
  • onError: Callback on error
  • All standard RequestInit options (method, headers, body, etc.)

Return Value

data: T | null

The fetched data, or null if not yet loaded

error: Error | null

Error object if the fetch failed

loading: boolean

True while the request is in progress

refetch: () => void

Function to manually trigger a new fetch

💡 Use Cases

  • Fetching user profiles and data
  • Loading lists and collections
  • API integration with loading states
  • Conditional data fetching
  • Real-time data refresh
  • RESTful API consumption