Docs
API

Request Configuration

💡

This page covers the API specific to http-react, not the Fetch API (opens in a new tab). For more about the Fetch API, see Using the Fetch API (opens in a new tab).

Return Values

useFetch returns an object with the following properties:

  • data - The response data. During loading, returns either the last resolved value or the default value
  • config - The configuration used for the request
  • id - The unique identifier for the request
  • isPending | isLoading - Loading state of the request
  • refresh - Function to revalidate and resend the request (only works if the request has completed)
  • response - The raw response object
  • responseTime - Request completion time in milliseconds
  • online - Internet connection status (web only)
  • mutate - Function to update data locally for optimistic UI updates
  • fetcher - Object with methods (get, post, delete, etc.) for imperative fetching using the same configuration
  • error - Error state of the request
  • code - HTTP status code
  • abort - Function to cancel an in-progress request
  • requestStart - Date when the request was sent (or null)
  • requestEnd - Date when the request completed (or null)
  • expiration - Date when cached data expires (or null if maxCacheAge is 0 ms)
  • success - true if the request completed successfully and is not loading
  • hasData - true if data is not null or undefined
  • loadingFirst - true if this is the first request and it's loading
💡

If props change while a request is loading, the current request is cancelled and a new one is sent with the updated props. This does not apply to server actions.

Configuration Options

Adding Static Typing

type ResponseType = {
  items: []
}
 
const { data } = useFetch<ResponseType>({ url: '/api' })

url

The request URL. Can be passed as the first argument or in the config object:

const { data } = useFetch('/api')

Or:

const { data } = useFetch({ url: '/api' })

key

Optional unique identifier for the request. Can be any serializable value. If not provided, a Key is generated from the method and url:

useFetch('/api', {
  key: 'API'
})
💡

The request Key will be 'API'. Without a custom Key, it would be 'GET /api'.

default

Default value returned while the request is loading. If cached data exists, it takes precedence:

const { data } = useFetch('/info', {
  default: {
    name: '',
    email: ''
  }
})
 
return <p>{data.name}</p>

TypeScript will infer the type of data from default. If you explicitly specify a type, TypeScript will validate that default matches that type.

baseUrl

Overrides the globally defined baseUrl:

const { data } = useFetch('/info', {
  baseUrl: '/api'
})
// Final URL: '/api/info'

maxCacheAge

Maximum time to cache data before it's considered expired. After expiration, a new request is sent. Works across navigation as long as the page isn't reloaded:

const { data, expiration } = useFetch('/info', {
  maxCacheAge: '0.5 h' // Cache for 30 minutes
})
 
return <p>Expires at: {expiration?.toLocaleTimeString()}</p>
💡

If maxCacheAge changes, cached data is automatically marked as expired and a new request is sent.

cacheIfError

If true (default), returns the last successful data when a request fails. If false, returns the default value or null:

const { data } = useFetch('/info', {
  cacheIfError: false,
  default: {} // Returned if request fails
})

body

Request body for POST, PUT, PATCH, DELETE, etc.:

const { data } = useFetch('/save-work', {
  method: 'POST',
  body: {
    title: 'My title',
    content: 'My content'
  }
})

By default, body is serialized as JSON. Use formatBody to customize serialization and set the Content-Type header.

formatBody

Customize how the request body is formatted. Example with uppercase transformation:

const { data } = useFetch('/save-work', {
  method: 'PATCH',
  body: {
    title: 'My title',
    content: 'My content'
  },
  formatBody(body) {
    return JSON.stringify({
      ...body,
      title: body.title.toUpperCase()
    })
  }
})

Example with FormData:

import { usePOST, revalidate } from 'http-react'
import { useState } from 'react'
 
export default function App() {
  const [formData, setFormData] = useState(new FormData())
 
  const { data, id } = usePOST('/api/user-info', {
    auto: false,
    body: formData,
    headers: {
      'Content-Type': 'multipart/form-data'
    },
    formatBody: (rawBody) => rawBody // Send as FormData
  })
 
  return (
    <div>
      <button onClick={() => revalidate(id)}>Save image</button>
      {data && <img src={data.url} alt={data.description} />}
    </div>
  )
}

params

URL parameters using bracket or colon notation:

const { data } = useFetch('/todos/[id]', {
  params: {
    id: 3
  }
})

Final URL: '/todos/3'. Request Key: 'GET /todos/[id]'

Colon notation:

const { data } = useFetch('/todos/:id', {
  params: {
    id: 3
  }
})

Mixed notation:

const { data } = useFetch('/[resource]/:id', {
  params: {
    resource: 'todos',
    id: 3
  }
})

Missing parameters trigger console warnings and remain unparsed.

Helper function for parsing parameters:

import { setURLParams } from 'http-react'
 
const userParams = { path: 'users', id: 10 }
 
const userUrl = setURLParams('/api/[path]/[id]', userParams) // '/api/users/10'
💡
Parameters must be separated by /

query

URL search parameters:

const { data } = useFetch('/search', {
  query: {
    start_date: '2023-01-02',
    end_date: '2023-01-03'
  }
})
// Final URL: '/search?start_date=2023-01-02&end_date=2023-01-03'

cancelOnChange and onAbort

By default, requests are cancelled when props change. Use onAbort to handle cancellations:

const [page, setPage] = useState(1)
 
const { data } = useFetch('/items', {
  cancelOnChange: true,
  onAbort() {
    console.log('Request cancelled')
  },
  query: {
    page // Changing page cancels the current request
  }
})

refresh

Automatically resend requests after a specified interval following completion. Accepts milliseconds or a string with units:

  • ms - milliseconds
  • sec - seconds
  • min - minutes
  • h - hours
  • d - days
  • we - weeks
  • mo - months
  • y - years

These units also apply to attemptInterval and debounce.

const { data } = useFetch('/api', {
  refresh: '5 sec', // Revalidate 5 seconds after completion
  default: {}
})
 
return (
  <div>
    <h2>Refreshing every 5 seconds</h2>
    <p>{data.name}</p>
  </div>
)

Fractional values:

const { data } = useFetch('/api', {
  refresh: '0.5 h', // Same as '30 min'
  default: {}
})

onResolve

Runs once when a request completes successfully:

useFetch('/api', {
  onResolve(data) {
    console.log('Data loaded:', data)
  }
})

Use useResolve to subscribe to request completion from other components:

// Component A
useFetch('/api', { method: 'POST' })
 
// Component B
useResolve('POST /api', (data) => {
  console.log('Data fetched from another component:', data)
})
💡

Important: onResolve runs only once per request, even when reused across multiple components. Use useResolve for multiple subscribers.

Multiple subscribers example:

const requestId = 'POST /api'
 
useResolve(requestId, (data) => {
  console.log('Subscriber 1:', data)
})
 
useResolve(requestId, (data) => {
  console.log('Subscriber 2:', data)
})
 
useResolve(requestId, (data) => {
  console.log('Subscriber 3:', data)
})
This pattern also applies to onError.

onError

Runs when a request fails:

useFetch('/api', {
  onError(error) {
    console.log('Error occurred:', error)
  }
})

Subscribe to errors from other components:

// Component A
useFetch('/api', { method: 'POST' })
 
// Component B
const error = useError('POST /api', () => {
  console.log('Request failed')
})
 
if (error) return <p>Error</p>

onOffline

Runs when internet connection is lost:

useFetch('/api', {
  onOffline() {
    alert('You are offline')
  }
})

onOnline

Runs when internet connection is restored:

useFetch('/api', {
  onOnline() {
    alert('Back online')
  }
})

retryOnReconnect

If true (default), automatically retries the request when connection is restored:

useFetch('/api', {
  retryOnReconnect: true
})

revalidateOnFocus

If true, resends the request when the window regains focus:

useFetch('/api', {
  revalidateOnFocus: true
})
💡

Requests won't be sent if one is already in progress. http-react tracks loading states automatically.

resolver

Customizes how response data is extracted. Default behavior parses JSON:

const { data } = useFetch('/api/cat.jpg', {
  async resolver(response) {
    const blob = await response.blob()
    return URL.createObjectURL(blob)
  }
})
 
return <img src={data} alt="Cat" />

Simplified using useBlob:

const { data } = useBlob('/api/cat.jpg', { objectURL: true })
 
return <img src={data} alt="Cat" />

When objectURL is true, data is an object URL (opens in a new tab). Otherwise, it's a Blob (opens in a new tab).

auto

If false, requests are only sent by calling reFetch or using the revalidate function:

const { data, reFetch } = useFetch('/api', {
  auto: false,
  default: {}
})
 
return (
  <div>
    <button onClick={reFetch}>Get data</button>
    <p>{data.name}</p>
  </div>
)
⚠️

Never set both auto: false and suspense: true — the request will never be sent and the fallback UI will persist indefinitely.

debounce (unstable)

Delays requests until a specified time after props stop changing:

import { useState } from 'react'
import useFetch from 'http-react'
 
export default function ExpensiveSearch() {
  const [search, setSearch] = useState('')
 
  const { data } = useFetch('/search', {
    auto: search.length > 0,
    default: [],
    query: {
      q: search
    },
    debounce: '2 sec' // Wait 2 seconds after changes
  })
 
  return (
    <div>
      <input
        value={search}
        onChange={(e) => setSearch(e.target.value)}
        placeholder='Search items'
      />
      {search ? (
        <p>Found: {data.length} items</p>
      ) : (
        <p>Start typing...</p>
      )}
    </div>
  )
}
💡

Using debounce directly is unstable. Use useDebounceFetch instead.

attempts

Number of retry attempts after a failed request. Resets after successful completion:

const { data, reFetch } = useFetch('/api', {
  attempts: 4,
  default: {}
})
 
return (
  <div>
    <button onClick={reFetch}>Refresh</button>
    <p>{data.name}</p>
  </div>
)

attemptInterval

Time interval between retry attempts. Default is 2 ms:

const { data, reFetch, online } = useFetch('/api', {
  attempts: 4,
  attemptInterval: '2 sec',
  default: {}
})
// Retry 4 times with 2-second intervals
 
return (
  <div>
    <button onClick={reFetch}>Refresh</button>
    <p>{data.name}</p>
    {online ? <p>Server is up</p> : <p>Server may be down</p>}
  </div>
)

memory (deprecated)

💡

Deprecated for consistency. Initial data is now always returned from cache.

When false, initial data always comes from the default property instead of cache:

const { data, reFetch } = useFetch('/api', {
  memory: false,
  default: {}
})
 
return (
  <div>
    <button onClick={reFetch}>Refresh</button>
    <p>{data.name}</p>
  </div>
)
⚠️

Setting memory: false is not recommended as it can cause layout shifts (opens in a new tab) and inconsistent UI.

revalidateOnMount

Controls whether requests are resent when a component remounts with unchanged props. Useful for:

  • Requests that should only be sent once per application lifecycle
  • Preserving data when navigating back/forward
  • Expensive operations that shouldn't repeat unnecessarily
const { data, reFetch } = useFetch('/some-expensive-resource', {
  revalidateOnMount: false,
  default: {
    name: ''
  }
})
 
return (
  <div>
    <button onClick={reFetch}>Refresh</button>
    <p>{data.name}</p>
  </div>
)

When this doesn't work:

  • Props depend on component-level state that resets between renders
  • Props include values from Math.random(), Date.now(), or crypto.randomUUID() (unless memoized)

Example of problematic usage:

import useFetch from 'http-react'
 
function WillAlwaysRevalidateOnMount() {
  const [page, setPage] = useState(1)
 
  const { data, reFetch } = useFetch('/some-expensive-resource', {
    revalidateOnMount: false,
    default: {
      name: ''
    },
    id: Math.random(), // Random ID causes revalidation every time
    query: {
      page
    }
  })
 
  return (
    <div>
      <button onClick={reFetch}>Refresh</button>
      <p>{data.name}</p>
    </div>
  )
}

Solution using state management:

import useFetch from 'http-react'
import { atom, useAtom } from 'atomic-state'
 
const pageState = atom({
  key: 'page',
  default: 1
})
 
function WillNotNecessarilyRevalidateOnMount() {
  const [page, setPage] = useAtom(pageState)
 
  const { data, reFetch } = useFetch('/some-expensive-resource', {
    revalidateOnMount: false,
    default: {
      name: ''
    },
    query: {
      page
    }
  })
 
  return (
    <div>
      <button onClick={reFetch}>Refresh</button>
      <p>{data.name}</p>
    </div>
  )
}

Atomic State (opens in a new tab) provides a useState-like API with persistent state management across component lifecycles.

onPropsChange

Runs when props passed to useFetch change:

function App() {
  const [page, setPage] = useState(1)
 
  const { data } = useFetch('/items', {
    onPropsChange({ previousProps, props }) {
      console.log('Props changed from', previousProps, 'to', props)
    },
    query: {
      page
    },
    default: []
  })
 
  return (
    <div>
      <button onClick={() => setPage((prev) => prev + 1)}>
        Next page
      </button>
      <p>Total items: {data.length}</p>
    </div>
  )
}

suspense

Enables React Suspense for the request. Useful for coordinating loading states:

import { Suspense } from 'react'
import useFetch from 'http-react'
 
function Profile() {
  const { data } = useFetch('/api/v2/profile', {
    headers: {
      Authorization: 'Token my-token'
    },
    suspense: true
  })
 
  return (
    <div>
      <p>Name: {data.name}</p>
      <p>Email: {data.email}</p>
    </div>
  )
}
 
export default function App() {
  return (
    <div>
      <h2>My profile</h2>
      <Suspense fallback={<p>Loading profile...</p>}>
        <Profile />
      </Suspense>
    </div>
  )
}

Suspense with SSR:

Use SSRSuspense to prevent hydration errors while showing fallback UI:

import useFetch, { SSRSuspense } from 'http-react'
 
function Profile() {
  const { data } = useFetch('/api/v2/profile', {
    headers: {
      Authorization: 'Token my-token'
    },
    suspense: true
  })
 
  return (
    <div>
      <p>Name: {data.name}</p>
      <p>Email: {data.email}</p>
    </div>
  )
}
 
export default function App() {
  return (
    <div>
      <h2>My profile</h2>
      <SSRSuspense fallback={<p>Loading profile...</p>}>
        <Profile />
      </SSRSuspense>
    </div>
  )
}

cacheProvider

Custom cache implementation for storing request data. Must implement:

export type CacheStoreType = {
  get(k?: any): any
  set(k?: any, v?: any): any
  remove?(k?: any): any
}

Example using localStorage via atomic-state:

import useFetch from 'http-react'
import { storage } from 'atomic-state'
 
export default function App() {
  const { data } = useFetch('/api/user-info', {
    refresh: '20 sec',
    cacheProvider: storage
  })
 
  return (
    <div>
      <h2>Data cached in localStorage</h2>
      <pre>{JSON.stringify(data, null, 2)}</pre>
    </div>
  )
}