Request config
This page covers the API specific to http-react
, not the Fetch
API (opens in a new tab). If you want
to learn more about it, check out Using the Fetch
API (opens in a new tab)
useFetch
returns an object with this information about the request:
data
: The response data. While data is loading, the returned value will be either the last resolved value (if it exists) or thedefault
passed touseFetch
config
: The configuration used to send the requestid
: The id of the requestisPending
|isLoading
: The loading state of the requestrefresh
: A function to revalidate and send the request again (this won't work if the request hasn't completed yet)response
: The response objectresponseTime
: The time it took to complete the request (in miliseconds)online
: If the devices was connected or disconected from the internet (web)mutate
: A function (a state setter) to mutate data locally (optimistic UI)fetcher
: An object with diferent methods (get
,post
,delete
, etc) that lets you fetch data imperatively using the configuration passed touseFetch
(similar toAxios
)error
: The error state of the requestcode
: The response status codeabort
: A function that lets you cancel the request if it hasn't completed yetrequestStart
: ADate
object of the time the request was sent (null
if it doesn't exist yet)requestEnd
: ADate
object of the time the request finished (null
if it doesn't exist yet)expiration
: ADate
object of the time the last fetched data will expire (by default it returnsnull
becausemaxCacheAge
default value is0 ms
)success
: Will betrue
if the request completed succesfuly andloading
isfalse
hasData,
: This istrue
Ifdata
is notnull
orundefined
loadingFirst
: Will betrue
if the request is being sent for the first time andloading
istrue
If props passed to useFetch
change while a request is loading, the current
request will be cancelled and a new request with the last props will be sent
(data dependency). However, this is not the case with server actions
By passing additional configuration to useFetch
, you can configure how data is fetched or decide what to do when certain events happen.
Adding static typing:
type ResponseType = {
items: []
}
const { data } = useFetch<ResponseType>({ url: '/api' })
url
This is the most important part of the request, and you can pass it in two ways:
const { data } = useFetch('/api')
Or
const { data } = useFetch({ url: '/api' })
id
An optional unique id for the request. It can be anything that can be serialized. If id
is not provided, an id will be created internally using the request method
and the url
. For example:
useFetch('/api', {
id: 'API'
})
The id of that request will be API
. If you wouldn't have passed it, it would
be GET /api
default
The default value that will be returned while the request is completing. If a cache in the cacheProvider
exists for that particular request, that will be returned instead.
const { data } = useFetch('/info', {
default: {
name: '',
email: ''
}
})
return <p>{data.name}</p>
In this case, the TypeScript type of data
will be inferred from default
.
However, if a type was specified (see here),
this would show an error if the type of default
doesn't match
baseUrl
Override the baseUrl
defined gloabally (we'll cover that later)
const { data } = useFetch('/info', {
baseUrl: '/api'
})
// The final url will be '/api/info'
maxCacheAge
The max time a cached version of the data should be stored in the cacheProvider
. After this time, the data will be considered as expired
and a new request will be sent. If data has not expired, no request will be sent. This still works when navigating between components and pages (as long as the page is not reloaded). If cacheProvider
is based on persistence, reloading the page or restarting the app will not revalidate if the data has not expired.
const { data, expiration } = useFetch('/info', {
maxCacheAge: '0.5 h' // Data will be valid for 30 minutes.
})
return <p>Data will expire at: {expiration?.toLocaleTimeString()}</p>
Note: If maxCache
age changes at some point, the cached data will be
automatically marked as expired
and a new request will be sent.
cacheIfError
If true
, the last resolved data
will be returned if a request fails. Otherwhise, the default
value passed will be returnd. If there is no default
value, null
will be returned instead:
const { data } = useFetch('/info', {
cacheIfError: false, // Default is 'true'
default: {} // This will be returned if the request fails
})
body
The request body (for requests that use POST, DELETE etc)
const { data } = useFetch('/save-work', {
method: 'POST',
body: {
title: 'My title',
content: 'My content'
}
})
By default, body
will be serialized as JSON, but you can also change that
with formatBody
(you can set the Content-Type
header and
the formatBody
function)
formatBody
Configure how the request body is sent in the request. In this example, the title will be sent in uppercase
const { data } = useFetch('/save-work', {
method: 'PATCH',
body: {
title: 'My title',
content: 'My content'
},
formatBody(body) {
return JSON.stringify({
...body,
title: body.title.toUpperCase()
})
}
})
Another 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, // Send the request only when the button is clicked
body: formData,
headers: {
'Content-Type': 'multipart/formdata'
},
formatBody: (rawBody) => rawBody // This will send the body as FormData
})
return (
<div>
<button onClick={() => revalidate(id)}>Save image</button>
{data && <img src={data.url} alt={data.description} />}
</div>
)
}
params
URL params (like Express)
const { data } = useFetch('/todos/[id]', {
params: {
id: 3
}
})
The url will be /todos/3
, and the request id will be GET /todos/[id]
You can also use :
const { data } = useFetch('/todos/:id', {
params: {
id: 3
}
})
Or both
const { data } = useFetch('/[resource]/:id', {
params: {
resource: 'todos',
id: 3
}
})
If a param does not exists, you will get a warning in the console, and it will not be parsed. For example:
const { data } = useFetch('/[resource]/:id', {
methos: 'POST',
params: {
id: 3
}
})
That will show you a warning in the console saying Param 'resource' does not exist in request configuration for '/[resource]/:id'
The url will be resource/3
, and the id
will be POST /[resource]/:id
http-react
also exposes a helper function to parse request params in a string:
import { setURLParams } from 'http-react'
const userParams = { path: 'users', id: 10 }
const userUrl = setURLParams('/api/[path]/[id]', userParams) // --> /api/users/10
/
query
The request URL search params
const { data } = useFetch('/search', {
query: {
start_date: '2023-01-02',
end_date: '2023-01-03'
}
})
// The url will be '/search?start_date=2023-01-02&end_date=2023-01-03'
cancelOnChange
and onAbort
cancelOnChange
is true
by default. This will cancel the current request when props change. The onAbort
function will run when the request gets cancelled. This is a pagination example
const [page, setPage] = useState(1)
const { data } = useFetch('/items', {
cancelOnChange: true,
onAbort() {
console.log('The request was cancelled')
},
config: {
query: {
// every time page changes, the current request will be cancelled
page
}
}
})
refresh
This tells useFetch
how much time should pass after a request is completed to make a new request.
This means that a new request will be sent x
amount of time after the current request is completed.
Refresh can be a number (if so, it will be taken as miliseconds) or a string. You can pass a number and a unit of time separated by space. These units are:
ms
-> milisecondssec
-> secondsmin
-> minutesh
-> hoursd
-> dayswe
-> weeksmo
-> monthsy
-> years
These units of time also apply to attemptInterval
and
debounce
// This will revalidate 5 seconds after the current request completes
const { data } = useFetch('/api', {
refresh: '5 sec',
default: {}
})
return (
<div>
<h2>Refreshing every 5 seconds</h2>
<p>{data.name}</p>
</div>
)
Another example:
// This would be the same as passing '30 min'
const { data } = useFetch('/api', {
refresh: '0.5 h',
default: {}
})
onResolve
The onResolve
function will once run when the request completes succesfuly
useFetch('/api', {
onResolve(data) {
console.log('Data loaded', data)
}
})
You can also use the useResolve
function to subscribe to requests and do something when they complete susccesfuly (this works like a useEffect that runs when the request completes).
To do this, pass the request id to useResolve
:
// somewhere in a component
useFetch('/api', { method: 'POST' })
// Somewhere else in another component
useResolve('POST /api', (data) => {
console.log('Data was fetched from another component', data)
})
Important
onResolve
will only run one time when the request completes, even if you are re-using a request in multiple components. This ensures that operations that depend on onResolve
run only once.
If you want to do any operations that can't be place in the onResolve
function for any reason, or want to add effects that run when the request completes, you can use the useResolve
hook instead.
In this example, three effects are subscribed to a request with id POST /api
:
const requestId = 'POST /api'
useResolve(requestId, (data) => {
console.log('Data was fetched from another component', data)
})
useResolve(requestId, (data) => {
console.log('Another subscriber', data)
})
useResolve(requestId, (data) => {
console.log('Next.js is awesome!', data)
})
onError
onError
The onError
function will run when the request fails
useFetch('/api', {
onError(error) {
console.log('An error ocurred', error)
}
})
Same as onResolve
, you can subscribe to the error
state somewhere else in your app:
// somewhere in your app
useFetch('/api', { method: 'POST' })
// Somewhere else in your app
const error = useError('POST /api', () => {
console.log('An error ocurred in this request')
})
if (error) return <p>Error</p>
return null
onOffline
The onOffline
function will run when internet conection is lost
useFetch('/api', {
onOffline() {
alert('You are offline')
}
})
onOnline
The onOnline
function will run when internet conection is restored
useFetch('/api', {
onOnline() {
alert('Back online')
}
})
retryOnReconnect
If true
(default), a new request will be sent when the conection is restored after a disconection
useFetch('/api', {
retryOnReconnect: true
})
revalidateOnFocus
If true
(default is false
), a new request will be sent when the window is focused
useFetch('/api', {
revalidateOnFocus: true
})
A new request won't be sent if there is already a request running.
http-react
keeps track of requests that are still loading.
resolver
The resolver
function returns the value that will be returned as data
. By default, it tries to parse the response body as JSON.
const { data } = useFetch('/api/cat.jpg', {
async resolver(response) {
const blob = await response.blob()
return URL.createObjectURL(blob)
}
})
return <img src={data} />
The example above can be replaced with the useBlob
hook, which is exactly the same, but converts the request into a blob:
const { data } = useBlob('/api/cat.jpg', { objectURL: true })
return <img src={data} />
In this example, objectURL
is true
because what the UI neededs is an
object
URL (opens in a new tab) of
the image. If you don't pass objectURL
, data
will be a
Blob
(opens in a new tab)
auto
If auto
is set to false, requests will only be sent by calling the reFetch
function returned from useFetch
or using the revalidate
function from anywhere in your app
const { data, reFetch } = useFetch('/api', {
auto: false,
default: {}
})
return (
<div>
<button onClick={reFetch}>Get data</button>
<p>{data.name}</p>
</div>
)
Don't set auto
to false
and suspense
to true
in the same request,
because it will never be sent and the UI will always show fallback
!
debounce
(unstable)
With debounce, a request will be debounced by x
ammount of time after props passed to useFetch
change.
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, // only if search is not empty
default: [],
query: {
q: search
},
debounce: '2 sec' // Wait for 2 seconds, then send a new request
})
return (
<div>
<input
value={search}
onClick={(e) => setSearch(e.target.value)}
placeholder='Search items'
/>
{search ? (
<p>Total items found: {data.length}</p>
) : (
<p>Start typing something</p>
)}
</div>
)
}
Note: Using debounce
directly inside useFetch
is unstable. Use the
useDebounceFetch
hook instead.
See Debounce – How to Delay a Function in JavaScript (JS ES6 Example) (opens in a new tab) by Ondrej Polesny
attempts
This tells useFetch
how many times a request should be sent again if the initial request fails. This number is reset after a request completes succesfuly so it can be reused in subsequent failed requests.
const { data, reFetch } = useFetch('/api', {
attempts: 4,
default: {}
})
return (
<div>
<button onClick={reFetch}>Refresh data</button>
<p>{data.name}</p>
</div>
)
attemptInterval
This is the time interval between each attempt after a request fails. By default it's 2 ms
const { data, reFetch, online } = useFetch('/api', {
attempts: 4,
attemptInterval: '2 sec',
default: {}
})
// If the request fails, retry 4 times with an interval of 2 seconds between each attempt
return (
<div>
<button onClick={reFetch}>Refresh data</button>
<p>{data.name}</p>
{online ? <p>Server is up</p> : <p>The server may be down</p>}
</div>
)
memory
(deprecated)
For consistency, this feature was deprecated as initial data is returned from cache.
By default it's true
. If false
, the initial data will always be taken from the default
property and not from cacheProvider
or in-memory cache.
const myResponse = useFetch('/api', {
memory: false,
default: {}
})
const { data, reFetch, online } = myResponse
return (
<div>
<button onClick={reFetch}>Refresh data</button>
<p>{data.name}</p>
</div>
)
Setting memory
to false
is not recommended because it can lead to layout
shifts (opens in a new tab)
(which can end up in a bad UX!) and would make the UI less
consistent (opens in a new tab)
revalidateOnMount
This tells useFetch
whether or not a request should be sent again when the component initializing it is re-mounted but the props used in that request haven't changed.
This is very useful in certain scenarios. Some examples include:
- A request should be sent only once during the application lifecyle (opens in a new tab).
- A request should not be sent when going back or forward in the navigation (as long as previous props passed to
useFetch
are presereved in some way, or you are using Next.js's layouts (opens in a new tab)) - A request should be sent only when props change, even when navigating between components or pages (in Web, as long as navigating between pages doesn't trigger a full page reload)
const { data, reFetch } = useFetch('/some-expensive-resource', {
revalidateOnMount: false,
default: {
name: ''
}
})
return (
<div>
<button onClick={reFetch}>Request expensive data again</button>
<p>{data.name}</p>
</div>
)
When is this not helpful?
- Props passed to
useFetch
depend on component-level state that is reset between renders - Props passed to
useFetch
change between renders (e.g. they receive a value that depends onMath.random()
,Date.now()
orcrypto.randomUUID()
. However, if that value is preserved, for example, by usinguseMemo
(opens in a new tab), it will work)
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(),
query: {
page
}
})
return (
<div>
<button onClick={reFetch}>Request expensive data again</button>
<p>{data.name}</p>
</div>
)
}
To prevent this, you can use a state management library (or Context) that preserves that state. For example, you can use atomic-state (opens in a new tab) and rewrite that component:
import useFetch from 'http-react'
import { atom, useAtom } from 'atomic-state'
const pageState = atom({
name: 'page',
default: 1
})
function WillNotNecesarilyRevalidateOnMount() {
const [page, setPage] = useAtom(pageState)
const { data, reFetch } = useFetch('/some-expensive-resource', {
revalidateOnMount: false,
default: {
name: ''
},
query: {
page
}
})
return (
<div>
<button onClick={reFetch}>Request expensive data again</button>
<p>{data.name}</p>
</div>
)
}
Atomic State
is a state management library that provides an API that is very
similar to useState
(opens in a new tab) in React
with hooks (>=16.8
) and has many features that can make state management
easier. Read more at Atomic State (opens in a new tab)
onPropsChange
This function will run when the props passed to useFetch
change
function App() {
const [page, setPage] = useState(1)
const { data, reFetch } = useFetch('/items', {
onPropsChange({ previousProps, props }) {
console.log('Props changed from', previousProps, 'to', props)
},
query: {
page
},
default: []
})
return (
<div>
<button
onClick={() => {
setPage((previousPage) => previousPage + 1)
}}
>
Next page
</button>
<p>Total items: {data.length}</p>
</div>
)
}
suspense
Suspense
is used when you want to pause the rendering of a component and resume it until some data is ready (usually, this is when an asynchronus operation like an HTTP Request
triggered by the component is completed). This is very useful if, for example, you want to wait until all requests are completed to render the UI to the user, or to leverage the loading state of the UI to <React.Suspense>
(opens in a new tab). If you want to use Suspense, pass suspense
to the useFetch
config:
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>Your name: {data.name}</p>
<p>Your 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
If you are using SSR, you can use SSRSuspense
. It's a wrapper component around React.Suspense
that can prevent server/hydration errors while still showing fallback
in the SSR page:
import useFetch, { SSRSuspense } from 'http-react'
function Profile() {
const { data } = useFetch('/api/v2/profile', {
headers: {
Authorization: 'Token my-token'
},
suspense: true
})
return (
<div>
<p>Your name: {data.name}</p>
<p>Your 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
This is an object that can be used as a local cache. It should have the following shape:
// The actual type definition that you can import from 'http-react'
export type CacheStoreType = {
get(k?: any): any
set(k?: any, v?: any): any
remove?(k?: any): any
}
For example, you can use the storage
object provided by atomic-state
(opens in a new tab). It's a wrapper around Window.localStorage
(opens in a new tab):
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>This data is cached in localStorage</h2>
<pre>{JSON.stringify(data, null)}</pre>
</div>
)
}