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.
import useFetch from'uf'functionTodos() {
// this will run a GET to /todos on mount, and load the data into `todos`const [todos = [], setTodos, todosAPI] = useFetch('/todos')
const [adding, setAdding] = useState(false)
asyncfunctionaddTodo() {
setAdding(true)
// all methods inside `todosAPI` are lazy.// Aka, you must call `setTodos` to update `todos`// AND you must handle your own loading stateconst { data: newTodo, ok } = await todosAPI.post({ title: 'my new todo' })
// add the newTodo to the front of the listif (ok) setTodos([newTodo, ...todos])
setAdding(false)
}
return (
<><buttononClick={addTodo}>{adding ? 'Adding Todo...' : 'Add Todo'}</button>
{todosAPI.error && 'Error!'}
{todosAPI.loading && 'Loading Initial Todos...'}
{todos.map(todo => <divkey={todo.id}>{todo.title}</div>}
</>
)
}
Basic Usage useFetch
This fetch is run onMount/componentDidMount by default. If no method is specified, GET is the default.
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'functionTodos() {
const perPage = 15const [page, setPage] = useState(1)
// aka: load initial todos on mount into `data`. Re-runs when url changesconst [todos = [],, todosAPI] = useFetch(`/todos?page=${page}&perPage=${perPage}`, {
// appends newly fetched todosonNewData: (currTodos = [], newTodos) => [...currTodos, ...newTodos],
perPage, // stops making more requests if last todos fetched < 15.
}) // `perPage` REQUIRED if you want loadingMore to work properlyreturn (
<>
{todosAPI.error && 'Error!'}
{todos.map(todo => (
<divkey={todo.id}>{todo.title}</div>
)}
{!todosAPI.loading && 'Loading Initial Todos...'}
<buttononClick={() => setPage(page + 1)}>
{todoAPI.loadingMore ? 'Loading More...' : 'Load More'}
</button></>
)
}
constApp = () => (
<Providerurl='https://example.com'><Todos /></Provider>
)
Or if you want more control you can do
functionTodos() {
const perPage = 15const page = useRef(1)
// aka: load initial todos on mount into `todos`const [todos = [], setTodos, todosAPI] = useFetch('/todos')
const [loadingMore, setLoadingMore] = useState(false)
asyncfunctionloadMore() {
const hasMore = todos.length % perPage === 0if (!hasMore) returnsetLoadingMore(true)
const { data: moreTodos, ok } = await todosAPI.get(`?page=${++page.current}&perPage=${perPage}`)
if (ok) {
// setTodos would use the cache key `/todos` and save this into cachesetTodos(todos => [...todos, ...moreTodos])
}
setLoadingMore(false)
}
return (
<>
{todosAPI.error && 'Error!'}
{todos.map(todo => (
<divkey={todo.id}>{todo.title}</div>
)}
{!todosAPI.loading && 'Loading Initial Todos...'}
<buttononClick={loadMore}>{loadingMore ? 'Loading More...' : 'Load More'}</button></>
)
}
Destructured useFetch
var [data, setData, api] = useFetch('/api')
// want to use object destructuring? You can do that toovar { data, setData, ...api } = useFetch('/api')
const {
loading,
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.get()
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'functionApp() {
let [token, setToken] = useLocalStorage('token')
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 returnedrequest: async ({ options, url, path, route }) => {
if (isExpired(token)) {
token = awaitgetNewToken()
setToken(token)
}
options.headers.Authorization = `Bearer ${token}`return options
},
// every time we make an http request, before getting the response back, this will runresponse: async ({ 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 (res.data) res.data = toCamel(res.data)
return res
}
}
}
return (
<Providerurl='http://example.com'options={options}><SomeComponent /><Provider/>
)
}
File Uploads (FormData)
This example shows how we can upload a file using useFetch.
import useFetch from'uf'constFileUploader = () => {
const [file, setFile] = useState()
const { post } = useFetch('https://example.com/upload')
constuploadFile = async () => {
const data = newFormData()
data.append('file', file)
if (file instanceofFormData) awaitpost(data)
}
return (
<div>
{/* Drop a file onto the input below */}
<inputonChange={e => setFile(e.target.files[0])} />
<buttononClick={uploadFile}>Upload</button></div>
)
}
Handling Different Response Types
This example shows how we can get .json(), .text(), .formData(), .blob(), .arrayBuffer(), and all the other http response methods. By default, useFetch 1st tries to call response.json() under the hood, if that fails it's backup is response.text(). If that fails, then you need a different response type which is where this comes in.
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.
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.
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.
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` fielddata: [],
// typically, `interceptors` would be added as an option to the `<Provider />`interceptors: {
request: async ({ options, url, path, route }) => { // `async` is not requiredreturn options // returning the `options` is important
},
response: async ({ response }) => {
// note: `response.data` is equivalent to `await response.json()`return response // returning the `response` is important
}
},
// set's the default for `loading` fieldloading: false,
// called when aborting the requestonAbort: () => {},
// runs when an error happens.onError: ({ error }) => {},
// this will allow you to merge the `data` for pagination.onNewData: (currData, newData) => {
return [...currData, ...newData]
},
// called when the request times outonTimeout: () => {},
// 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 againperPage: 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 outputresponseType: '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 outretries: 3,
// The time between retriesretryDelay: 10000,
// OR// Can be a function which is used if we want change the time in between each retryretryDelay({ attempt, error, response }) {
// exponential backoffreturnMath.min(attempt > 1 ? 2 ** attempt * 1000 : 1000, 30 * 1000)
// linear backoffreturn attempt * 1000
},
// make sure `retries` is set otherwise it won't retry// can retry on certain http status codesretryOn: [503],
// ORasyncretryOn({ attempt, error, response }) {
// retry on any network error, or 4xx or 5xx status codesif (error !== null || response.status >= 400) {
console.log(`retrying, attempt number ${attempt + 1}`);
returntrue;
}
},
// enables React Suspense modesuspense: true, // defaults to `false`// amount of time before the request get's canceled/abortedtimeout: 10000,
}
useFetch(options)
// OR
<Provider options={options}><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.
tests to make sure response.formData() and some of the other http response methods work properly
the onMount works properly with all variants of passing useEffect(fn, [request.get]) and not causing an infinite loop
async tests for interceptors.response
aborts fetch on unmount
does not abort fetch on every rerender
retryDelay and timeout are both set. It works, but is annoying to deal with timers in tests. resource
timeout with retries > 0. (also do retires > 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 and const request = useFetch(); request.subscribe() or something along those lines
figure out a good way to show side-by-side comparisons
show comparison with Axios
potential option ideas
const request = useFetch({
graphql: {
// all options can also be put in here// to overwrite those of `useFetch` for// `useMutation` and `useQuery`
},
// by default this is true, but if set to false// then we default to the responseType array of trying 'json' first, then 'text', etc.// hopefully I get some answers on here: https://bit.ly/3afPlJSresponseTypeGuessing: true,
// Allows you to pass in your own cache to useFetch// This is controversial though because `cache` is an option in the requestInit// and it's value is a string. See: https://developer.mozilla.org/en-US/docs/Web/API/Request/cache// One possible solution is to move the default `fetch`'s `cache` to `cachePolicy`.// I don't really like this solution though.// Another solution is to only allow the `cache` option with the `<Provider cache={new Map()} />`cache: newMap(),
// these will be the exact same ones as Apollo'scachePolicy: 'cache-and-network', 'network-only', 'cache-only', 'no-cache'// 'cache-first'// potential idea to fetch on server instead of just having `loading` state. Not sure if this is a good idea thoughonServer: true,
onSuccess: (/* idk what to put here */) => {},
// if you would prefer to pass the query in the configquery: `some graphql query`// if you would prefer to pass the mutation in the configmutation: `some graphql mutation`refreshWhenHidden: false,
})
// potential for causing a rerender after clearing cache if needed
request.cache.clear(true)
- useFetch - lazy, non-lazy, both [![](https://img.shields.io/badge/example-blue.svg)](https://codesandbox.io/s/usefetch-request-response-managed-state-ruyi3?file=/src/index.js) [![](https://img.shields.io/badge/video-red.svg)](https://www.youtube.com/wat
The npm package uf receives a total of 1 weekly downloads. As such, uf popularity was classified as not popular.
We found that uf demonstrated a not healthy version release cadence and project activity because the last version was released a year ago.It has 1 open source maintainer collaborating on the project.
Package last updated on 29 Apr 2020
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
In this segment of the Risky Business podcast, Feross Aboukhadijeh and Patrick Gray discuss the challenges of tracking malware discovered in open source softare.