Docs
API

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 the default passed to useFetch
  • config: The configuration used to send the request
  • id: The id of the request
  • isPending | isLoading: The loading state of the request
  • refresh: A function to revalidate and send the request again (this won't work if the request hasn't completed yet)
  • response: The response object
  • responseTime: 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 to useFetch (similar to Axios)
  • error: The error state of the request
  • code: The response status code
  • abort: A function that lets you cancel the request if it hasn't completed yet
  • requestStart: A Date object of the time the request was sent (null if it doesn't exist yet)
  • requestEnd: A Date object of the time the request finished (null if it doesn't exist yet)
  • expiration: A Date object of the time the last fetched data will expire (by default it returns null because maxCacheAge default value is 0 ms)
  • success: Will be true if the request completed succesfuly and loading is false
  • hasData,: This is true If data is not null or undefined
  • loadingFirst: Will be true if the request is being sent for the first time and loading is true
💡

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
💡
Params must be separated using /

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 -> miliseconds
  • sec -> seconds
  • min -> minutes
  • h -> hours
  • d -> days
  • we -> weeks
  • mo -> months
  • y -> 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)
})
This also applies to 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.


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 on Math.random(), Date.now() or crypto.randomUUID(). However, if that value is preserved, for example, by using useMemo (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>
  )
}