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 thedefaultvalueconfig- The configuration used for the requestid- The unique identifier for the requestisPending|isLoading- Loading state of the requestrefresh- Function to revalidate and resend the request (only works if the request has completed)response- The raw response objectresponseTime- Request completion time in millisecondsonline- Internet connection status (web only)mutate- Function to update data locally for optimistic UI updatesfetcher- Object with methods (get,post,delete, etc.) for imperative fetching using the same configurationerror- Error state of the requestcode- HTTP status codeabort- Function to cancel an in-progress requestrequestStart-Datewhen the request was sent (ornull)requestEnd-Datewhen the request completed (ornull)expiration-Datewhen cached data expires (ornullifmaxCacheAgeis0 ms)success-trueif the request completed successfully and is not loadinghasData-trueifdatais notnullorundefinedloadingFirst-trueif 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'/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- millisecondssec- secondsmin- minutesh- hoursd- dayswe- weeksmo- monthsy- 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)
})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.
Learn more: Debounce – How to Delay a Function in JavaScript (opens in a new tab) by Ondrej Polesny
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(), orcrypto.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>
)
}