useFetch
Main Documentation
npm i uf
Features
- SSR (server side rendering) support
- TypeScript support
- 2 dependencies (use-ssr, urs)
- GraphQL support (queries + mutations)
- Provider to set default
url
andoptions
- Request/response interceptors
- React Native support
- Aborts/Cancels pending http requests when a component unmounts
- Built in caching
- Persistent caching support
- Suspense support
- Retry functionality
Usage
Examples + Videos
- useFetch - lazy, non-lazy, both
- useFetch - request/response interceptors
- useFetch - retries, retryOn, retryDelay
- useFetch - abort, timeout, onAbort, onTimeout
- useFetch - persist, cache
- useFetch - cacheLife, cachePolicy
- useFetch - suspense
- useFetch - pagination
- useQuery - GraphQL
- useFetch - Next.js
- useFetch - create-react-app
Basic Usage (mixing lazy + non-lazy) useFetch
If the last argument of useFetch
is not a dependency array []
, then it will not fire until you call one of the http methods like get
, post
, etc.
{ // this will run a GET to /todos on mount, and load the // data into `todos`. AKA NON-LAZY const todos = setTodos todosAPI = const adding setAdding = { // all methods inside `todosAPI` are LAZY. // Aka, you must call `setTodos` to update `todos` // AND you must handle your own loading state const data: newTodo ok = await todosAPI // add the newTodo to the front of the list if ok } return <> <button onClick=addTodo>adding ? 'Adding Todo...' : 'Add Todo'</button> todosAPIerror && 'Error!' todosAPIloading && 'Loading Initial Todos...' todos
Basic Usage useFetch
This fetch is run onMount/componentDidMount
by default. If no method is specified, GET is the default.
{ const options = {} // these options accept all native `fetch` options const todos = todosAPI = return <> todosAPIerror && 'Error!' todosAPIloading && 'Loading Initial Todos...' todos
Suspense Mode
Can put suspense
in 2 places. Either useFetch
(A) or Provider
(B).
{ // A. can put `suspense: true` here const todos = = return todos} { const options = suspense: true // B. can put `suspense: true` here too return <Provider url='https://example.com' options=options> <Suspense fallback='Loading...'> <Todos /> </Suspense> </Provider> }
Suspense Mode (lazy)
Can put suspense
in 2 places. Either useFetch
(A) or Provider
(B).
{ const todos = setTodos todosAPI = const loadInitialTodos = async { const data: todos ok = await todosAPI if ok } // componentDidMount return todos} { const options = suspense: true // B. can put `suspense: true` here too return <Provider url='https://example.com' options=options> <Suspense fallback='Loading...'> <Todos /> </Suspense> </Provider> }
Consider sponsoring
Ava, Rapid Application Development
Need a freelance software engineer with more than 5 years production experience at companies like Facebook, Discord, Best Buy, and Citrix?
website | email | twitter
Pagination/Infinite Scroll + Provider
The onNewData
will take the current data, and the newly fetched data, and allow you to merge the two however you choose. In the example below, we are appending the new todos to the end of the current todos.
import useFetch Provider from 'uf' { const perPage = 15 const page setPage = // aka: load initial todos on mount into `data`. Re-runs when url changes const todos = todosAPI = // `perPage` REQUIRED if you want loadingMore to work properly return <> todosAPIerror && 'Error!' todos const App = <Provider ='https://example.com'> <Todos /> </Provider>
Or if you want more control you can do
{ const perPage = 15 const page = // aka: load initial todos on mount into `todos` const todos = setTodos todosAPI = const loadingMore setLoadingMore = { const hasMore = todoslength % perPage === 0 if !hasMore return const data: moreTodos ok = await todosAPI if ok // setTodos would use the cache key `/todos` and save this into cache } return <> todosAPIerror && 'Error!' todos
Destructured useFetch
var data setData api = // want to use object destructuring? You can do that toovar data setData ...api = const loading // ONLY CHANGES WHEN CALLED VIA NON-LAZY error cache // methods: get, set, has, delete, clear (like `new Map()`) // lazy methods get post put patch delete // don't destructure `delete` though, it's a keyword mutate // GraphQL query // GraphQL abort} = api var data // the json value (or whatever responseType) of the response error // if we get a bad network call, this is the error path // in this case, would be `/api` // all the rest are normal fields of the JS Response class ...response = await api
Conditional useFetch
// var [, setTodos, todosAPI] = useFetch('/todos', { lazy: true })
Abort useFetch

const repos = setRepos reposAPI = // the line below is not isomorphic, but for simplicity we're using the browsers `encodeURI`const searching setSearching = const searchGithubRepos = { const data: repos ok = await reposAPI if ok } <> <input = /> <button =>Abort</button> searching ? 'Searching...' : repos</>
GraphQL Query useFetch
const QUERY = ` query Todos($userID string!) { todos(userID: $userID) { id title } }` { const user = userAPI = const loading setLoading = const getTodosForUser = async { await userAPI } return <> <button =>Get Todos</button> loading ? 'Loading...' : <pre>username</pre> </> }
Request/Response Interceptors
This example shows how we can do authentication in the request
interceptor and how we can camelCase the results in the response
interceptor
import Provider from 'uf'import toCamel from 'convert-keys' { let token setToken = const options = interceptors: // every time we make an http request, this will run 1st before the request is made // url, path and route are supplied to the interceptor // request options can be modified and must be returned request: if token = await optionsheadersAuthorization = `Bearer ` return options // every time we make an http request, before getting the response back, this will run response: // unfortunately, because this is a JS Response object, we have to modify it directly. // It shouldn't have any negative affect since this is getting reset on each request. const res = response if resdata resdata = return res return <Provider ='http://example.com' => <SomeComponent /> <Provider/> }
File Uploads (FormData)
This example shows how we can upload a file using useFetch
.
import useFetch from 'uf' const FileUploader = const file setFile = const post = const uploadFile = const data = data await return <div> /* Drop a file onto the input below */ <input = /> <button =>Upload</button> </div>
Overwrite/Remove Options/Headers Set in Provider
This example shows how to remove a header all together. Let's say you have <Provider url='url.com' options={{ headers: { Authentication: 'Bearer MY_TOKEN' } }}><App /></Provider>
, but for one api call, you don't want that header in your useFetch
at all for one instance in your app. This would allow you to remove that.
const Todos = { // let's say for this request, you don't want the `Accept` header at all const todos = todosAPI= return <> todosAPIerror && todosAPIerrormessge todosAPIloading && "Loading Initial Todos..." todos </> } const App = { const options = headers: Accept: 'application/json' return <Provider url='https://url.com' options=options><Todos /></Provider>}
Retries retryOn & retryDelay
In this example you can see how retryOn
will retry on a status code of 305
, or if we choose the retryOn()
function, it returns a boolean to decide if we will retry. With retryDelay
we can either have a fixed delay, or a dynamic one by using retryDelay()
. Make sure retries
is set to at minimum 1
otherwise it won't retry the request. If retries > 0
without retryOn
then by default we always retry if there's an error or if !response.ok
. If retryOn: [400]
and retries > 0
then we only retry on a response status of 400
.
const TodosRetry = { const todos = = return todos
Options
This is exactly what you would pass to the normal js fetch
, with a little extra. All these options can be passed to the <Provider options={/* every option below */} />
, or directly to useFetch
. If you have both in the <Provider />
and in useFetch
, the useFetch
options will overwrite the ones from the <Provider />
Option | Description | Default |
---|---|---|
cacheLife |
After a successful cache update, that cache data will become stale after this duration | 0 |
cachePolicy |
These will be the same ones as Apollo's fetch policies. Possible values are cache-and-network , network-only , cache-only , no-cache , cache-first . Currently only supports cache-first or no-cache |
cache-first |
data |
Allows you to set a default value for data |
undefined |
interceptors.request |
Allows you to do something before an http request is sent out. Useful for authentication if you need to refresh tokens a lot. | undefined |
interceptors.response |
Allows you to do something after an http response is recieved. Useful for something like camelCasing the keys of the response. | undefined |
loading |
Allows you to set default value for loading |
false unless the last argument of useFetch is [] |
onAbort |
Runs when the request is aborted. | empty function |
onError |
Runs when the request get's an error. If retrying, it is only called on the last retry attempt. | empty function |
onNewData |
Merges the current data with the incoming data. Great for pagination. | (curr, new) => new |
onTimeout |
Called when the request times out. | empty function |
persist |
Persists data for the duration of cacheLife . If cacheLife is not set it defaults to 24h. Currently only available in Browser. |
false |
perPage |
Stops making more requests if there is no more data to fetch. (i.e. if we have 25 todos, and the perPage is 10, after fetching 2 times, we will have 20 todos. The last 5 tells us we don't have any more to fetch because it's less than 10) For pagination. | 0 |
responseType |
This will determine how the data field is set. If you put json then it will try to parse it as JSON. If you set it as an array, it will attempt to parse the response in the order of the types you put in the array. Read about why we don't put formData in the defaults in the yellow Note part here. |
['json', 'text', 'blob', 'readableStream'] |
retries |
When a request fails or times out, retry the request this many times. By default it will not retry. | 0 |
retryDelay |
You can retry with certain intervals i.e. 30 seconds 30000 or with custom logic (i.e. to increase retry intervals). |
1000 |
retryOn |
You can retry on certain http status codes or have custom logic to decide whether to retry or not via a function. Make sure retries > 0 otherwise it won't retry. |
[] |
suspense |
Enables React Suspense mode. example | false |
timeout |
The request will be aborted/cancelled after this amount of time. This is also the interval at which retries will be made at. in milliseconds. If set to 0 , it will not timeout except for browser defaults. |
0 |
const options = // accepts all `fetch` options such as headers, method, etc. // The time in milliseconds that cache data remains fresh. cacheLife: 0 // Cache responses to improve speed and reduce amount of requests // Only one request to the same endpoint will be initiated unless cacheLife expires for 'cache-first'. cachePolicy: 'cache-first' // 'no-cache' // set's the default for the `data` field data: // typically, `interceptors` would be added as an option to the `<Provider />` interceptors: request: // `async` is not required return options // returning the `options` is important response: // note: `response.data` is equivalent to `await response.json()` return response // returning the `response` is important // set's the default for `loading` field loading: false // called when aborting the request {} // runs when an error happens. {} // this will allow you to merge the `data` for pagination. { return ...currData ...newData } // called when the request times out {} // this will tell useFetch not to run the request if the list doesn't haveMore. (pagination) // i.e. if the last page fetched was < 15, don't run the request again perPage: 15 // Allows caching to persist after page refresh. Only supported in the Browser currently. persist: false // this would basically call `await response.json()` // and set the `data` and `response.data` field to the output responseType: 'json' // OR can be an array. It's an array by default. // We will try to get the `data` by attempting to extract // it via these body interface methods, one by one in // this order. We skip `formData` because it's mostly used // for service workers. responseType: 'json' 'text' 'blob' 'arrayBuffer' // amount of times it should retry before erroring out retries: 3 // The time between retries retryDelay: 10000 // OR // Can be a function which is used if we want change the time in between each retry { // exponential backoff return Math // linear backoff return attempt * 1000 } // make sure `retries` is set otherwise it won't retry // can retry on certain http status codes retryOn: 503 // OR async { // retry on any network error, or 4xx or 5xx status codes if error !== null || responsestatus >= 400 console; return true; } // enables React Suspense mode suspense: true // defaults to `false` // amount of time before the request get's canceled/aborted timeout: 10000 // OR<Provider =><ResOfYourApp /></Provider>
Who's using useFetch?
Does your company use use-http? Consider sponsoring the project to fund new features, bug fixes, and more.
Browser Support
If you need support for IE, you will need to add additional polyfills. The React docs suggest these polyfills, but from this issue we have found it to work fine with the react-app-polyfill
. If you have any updates to this browser list, please submit a PR!
![]() Edge |
![]() Firefox |
![]() Chrome |
![]() Safari |
![]() Opera |
---|---|---|---|---|
12+ | last 2 versions | last 2 versions | last 2 versions | last 2 versions |
Feature Requests/Ideas
If you have feature requests, submit an issue to let us know what you would like to see!
Todos
-
prefetching
-
global cache state management
-
optimistic updates
-
persist
support for React Native -
better loading state management. When using only 1 useFetch in a component and we use
Promise.all([get('/todos/1'), get('/todos/2')])
then don't have a loading true, loading false on each request. Just have loading true on 1st request, and loading false on last request. -
is making a gitpod useful here? 🤔
-
suspense
- triggering it from outside the
<Suspense />
component.- add
.read()
torequest
- or make it work with just the
suspense: true
option - both of these options need to be thought out a lot more^
- add
- tests for this^ (triggering outside)
- cleanup tests in general. Snapshot tests are unpredictably not working for some reason.
- snapshot test resources: swr, react-apollo-hooks
- basic test resources: fetch-suspense, @testing-library/react-hooks suspense PR
- triggering it from outside the
-
maybe add translations like this one
-
maybe add contributors all-contributors
-
add sponsors similar to this
-
Error handling
- if calling
response.json()
and there is no response yet
- if calling
-
tests
- tests for SSR
- tests for react native see here
- tests for GraphQL hooks
useMutation
+useQuery
- tests for stale
response
see this PR - tests to make sure
response.formData()
and some of the other httpresponse methods
work properly - the
onMount
works properly with all variants of passinguseEffect(fn, [request.get])
and not causing an infinite loop -
async
tests forinterceptors.response
- aborts fetch on unmount
- does not abort fetch on every rerender
-
retryDelay
andtimeout
are both set. It works, but is annoying to deal with timers in tests. resource -
timeout
withretries > 0
. (also doretires > 1
) Need to figure out how to advance timers properly to write this and the test above
-
take a look at how react-apollo-hooks work. Maybe ad
useSubscription
andconst request = useFetch(); request.subscribe()
or something along those lines -
make this a github package
-
Documentation:
- show comparison with Apollo
- figure out a good way to show side-by-side comparisons
- show comparison with Axios
-
potential option ideas
const request =// potential for causing a rerender after clearing cache if neededrequestcache -
potential option ideas for
GraphQL
const request = `your graphql query`const request =const userID = 'some-user-uuid'const res = await request`query Todos($userID string!) {todos(userID: $userID) {idtitle}}` -
make code editor plugin/package/extension that adds GraphQL syntax highlighting for
useQuery
anduseMutation
😊 -
add React Native test suite