Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork116
🐶 React hook for making isomorphic http requests
License
ava/use-http
Folders and files
| Name | Name | Last commit message | Last commit date | |
|---|---|---|---|---|
Repository files navigation
Main Documentation
npm iuse-http
- SSR (server side rendering) support
- TypeScript support
- 2 dependencies (use-ssr,urs)
- GraphQL support (queries + mutations)
- Provider to set default
urlandoptions - Request/response interceptors
- React Native support
- Aborts/Cancels pending http requests when a component unmounts
- Built in caching
- Persistent caching support
- Suspense(experimental) support
- Retry functionality
- useFetch - managed state, request, response, etc.
- useFetch - request/response interceptors
- useFetch - retries, retryOn, retryDelay
- useFetch - abort, timeout, onAbort, onTimeout
- useFetch - persist, cache
- useFetch - cacheLife, cachePolicy
- useFetch - suspense(experimental)
- useFetch - pagination
- useQuery - GraphQL
- useFetch - Next.js
- useFetch - create-react-app
Basic Usage Managed StateuseFetch
If the last argument ofuseFetch is not a dependency array[], then it will not fire until you call one of the http methods likeget,post, etc.
importuseFetchfrom'use-http'functionTodos(){const[todos,setTodos]=useState([])const{ get, post, response, loading, error}=useFetch('https://example.com')useEffect(()=>{initializeTodos()},[])// componentDidMountasyncfunctioninitializeTodos(){constinitialTodos=awaitget('/todos')if(response.ok)setTodos(initialTodos)}asyncfunctionaddTodo(){constnewTodo=awaitpost('/todos',{title:'my new todo'})if(response.ok)setTodos([...todos,newTodo])}return(<><buttononClick={addTodo}>Add Todo</button>{error&&'Error!'}{loading&&'Loading...'}{todos.map(todo=>(<divkey={todo.id}>{todo.title}</div>))}</>)}
Basic Usage Auto-Managed StateuseFetch
This fetch is runonMount/componentDidMount. The last argument[] means it will runonMount. If you pass it a variable like[someVariable], it will runonMount and again wheneversomeVariable changes values (akaonUpdate). If no method is specified, GET is the default.
importuseFetchfrom'use-http'functionTodos(){constoptions={}// these options accept all native `fetch` options// the last argument below [] means it will fire onMount (GET by default)const{ loading, error, data=[]}=useFetch('https://example.com/todos',options,[])return(<>{error&&'Error!'}{loading&&'Loading...'}{data.map(todo=>(<divkey={todo.id}>{todo.title}</div>))}</>)}
Suspense Mode(experimental) Auto-Managed State
Can putsuspense in 2 places. EitheruseFetch (A) orProvider (B).
importuseFetch,{Provider}from'use-http'functionTodos(){const{data:todos=[]}=useFetch('/todos',{suspense:true// A. can put `suspense: true` here},[])// onMountreturntodos.map(todo=><divkey={todo.id}>{todo.title}</div>)}functionApp(){constoptions={suspense:true// B. can put `suspense: true` here too}return(<Providerurl='https://example.com'options={options}><Suspensefallback='Loading...'><Todos/></Suspense></Provider>)}
Suspense Mode(experimental) Managed State
Can putsuspense in 2 places. EitheruseFetch (A) orProvider (B). Suspense mode via managed state is very experimental.
importuseFetch,{Provider}from'use-http'functionTodos(){const[todos,setTodos]=useState([])// A. can put `suspense: true` hereconst{ get, response}=useFetch({suspense:true})constloadInitialTodos=async()=>{consttodos=awaitget('/todos')if(response.ok)setTodos(todos)}// componentDidMountuseEffect(()=>{loadInitialTodos()},[])returntodos.map(todo=><divkey={todo.id}>{todo.title}</div>)}functionApp(){constoptions={suspense:true// B. can put `suspense: true` here too}return(<Providerurl='https://example.com'options={options}><Suspensefallback='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 +Provider
TheonNewData 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.
importuseFetch,{Provider}from'use-http'constTodos=()=>{const[page,setPage]=useState(1)const{ data=[], loading}=useFetch(`/todos?page=${page}&amountPerPage=15`,{onNewData:(currTodos,newTodos)=>[...currTodos, ...newTodos],// appends newly fetched todosperPage:15,// stops making more requests if last todos fetched < 15},[page])// runs onMount AND whenever the `page` updates (onUpdate)return(<ul>{data.map(todo=><likey={todo.id}>{todo.title}</li>}{loading&&'Loading...'}{!loading&&(<buttononClick={()=>setPage(page+1)}>Load More Todos</button>)}</ul>)}constApp=()=>(<Providerurl='https://example.com'><Todos/></Provider>)
DestructureduseFetch
response object! Details inthis video. Technically you can do it, but if you need to access theresponse.ok from, for example, within a component's onClick handler, it will be a stale value forok where it will be correct forresponse.ok. ️️
var[request,response,loading,error]=useFetch('https://example.com')// want to use object destructuring? You can do that toovar{ request, response,// 🚨 Do not destructure the `response` object! loading, error, data, cache,// methods: get, set, has, delete, clear (like `new Map()`) get, post, put, patch, delete// don't destructure `delete` though, it's a keyworddel,// <- that's why we have this (del). or use `request.delete` head, options, connect, trace, mutate,// GraphQL query,// GraphQL abort}=useFetch('https://example.com')// 🚨 Do not destructure the `response` object!// 🚨 This just shows what fields are available in it.var{ ok, status, headers, data, type, statusText, url, body, bodyUsed, redirected,// methods json, text, formData, blob, arrayBuffer, clone}=responsevar{ loading, error, data, cache,// methods: get, set, has, delete, clear (like `new Map()`) get, post, put, patch, delete// don't destructure `delete` though, it's a keyworddel,// <- that's why we have this (del). or use `request.delete` mutate,// GraphQL query,// GraphQL abort}=request
AbortuseFetch

const{ get, abort, loading,data:repos}=useFetch('https://api.github.com/search/repositories?q=')// the line below is not isomorphic, but for simplicity we're using the browsers `encodeURI`constsearchGithubRepos=e=>get(encodeURI(e.target.value))<><inputonChange={searchGithubRepos}/><buttononClick={abort}>Abort</button>{loading ?'Loading...' :repos.data.items.map(repo=>(<divkey={repo.id}>{repo.name}</div>))}</>
GraphQL QueryuseFetch
constQUERY=` query Todos($userID string!) { todos(userID: $userID) { id title } }`functionApp(){constrequest=useFetch('http://example.com')constgetTodosForUser=id=>request.query(QUERY,{userID:id})return(<><buttononClick={()=>getTodosForUser('theUsersID')}>Get User's Todos</button>{request.loading ?'Loading...' :<pre>{request.data}</pre>}</>)}
GraphQL MutationuseFetch
TheProvider allows us to set a defaulturl,options (such as headers) and so on.
constMUTATION=` mutation CreateTodo($todoTitle string) { todo(title: $todoTitle) { id title } }`functionApp(){const[todoTitle,setTodoTitle]=useState('')constrequest=useFetch('http://example.com')constcreatetodo=()=>request.mutate(MUTATION,{ todoTitle})return(<><inputonChange={e=>setTodoTitle(e.target.value)}/><buttononClick={createTodo}>Create Todo</button>{request.loading ?'Loading...' :<pre>{request.data}</pre>}</>)}
Provider using the GraphQLuseMutation anduseQuery
import{useQuery}from'use-http'exportdefaultfunctionQueryComponent(){// can also do it this way:// const [data, loading, error, query] = useQuery`// or this way:// const { data, loading, error, query } = useQuery`constrequest=useQuery` query Todos($userID string!) { todos(userID: $userID) { id title } } `constgetTodosForUser=id=>request.query({userID:id})return(<><buttononClick={()=>getTodosForUser('theUsersID')}>Get User's Todos</button>{request.loading ?'Loading...' :<pre>{request.data}</pre>}</>)}
import{useMutation}from'use-http'exportdefaultfunctionMutationComponent(){const[todoTitle,setTodoTitle]=useState('')// can also do it this way:// const request = useMutation`// or this way:// const { data, loading, error, mutate } = useMutation`const[data,loading,error,mutate]=useMutation` mutation CreateTodo($todoTitle string) { todo(title: $todoTitle) { id title } } `constcreateTodo=()=>mutate({ todoTitle})return(<><inputonChange={e=>setTodoTitle(e.target.value)}/><buttononClick={createTodo}>Create Todo</button>{loading ?'Loading...' :<pre>{data}</pre>}</>)}
These props are defaults used in every request inside the<Provider />. They can be overwritten individually
import{Provider}from'use-http'importQueryComponentfrom'./QueryComponent'importMutationComponentfrom'./MutationComponent'functionApp(){constoptions={headers:{Authorization:'Bearer YOUR_TOKEN_HERE'}}return(<Providerurl='http://example.com'options={options}><QueryComponent/><MutationComponent/><Provider/> )}
Request/Response Interceptors
This example shows how we can do authentication in therequest interceptor and how we can camelCase the results in theresponse interceptor
import{Provider}from'use-http'import{toCamel}from'convert-keys'functionApp(){let[token,setToken]=useLocalStorage('token')constoptions={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}`returnoptions},// every time we make an http request, before getting the response back, this will runresponse:async({ response, request})=>{// 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.constres=responseif(res.data)res.data=toCamel(res.data)returnres}}}return(<Providerurl='http://example.com'options={options}><SomeComponent/><Provider/> )}
File Uploads (FormData)
This example shows how we can upload a file usinguseFetch.
importuseFetchfrom'use-http'constFileUploader=()=>{const[file,setFile]=useState()const{ post}=useFetch('https://example.com/upload')constuploadFile=async()=>{constdata=newFormData()data.append('file',file)if(fileinstanceofFormData)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 otherhttp response methods. By default,useFetch 1st tries to callresponse.json() under the hood, if that fails it's backup isresponse.text(). If that fails, then you need a different response type which is where this comes in.
importuseFetchfrom'use-http'constApp=()=>{const[name,setName]=useState('')const{ get, loading, error, response}=useFetch('http://example.com')consthandleClick=async()=>{awaitget('/users/1?name=true')// will return just the user's nameconsttext=awaitresponse.text()setName(text)}return(<><buttononClick={handleClick}>Load Data</button>{error&&error.messge}{loading&&"Loading..."}{name&&<div>{name}</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 youruseFetch at all for one instance in your app. This would allow you to remove that.
importuseFetchfrom'use-http'constTodos=()=>{// let's say for this request, you don't want the `Accept` header at allconst{ loading, error,data:todos=[]}=useFetch('/todos',globalOptions=>{deleteglobalOptions.headers.AcceptreturnglobalOptions},[])// onMountreturn(<>{error&&error.messge}{loading&&"Loading..."}{todos&&<ul>{todos.map(todo=><likey={todo.id}>{todo.title}</li>)}</ul>}</>)}constApp=()=>{constoptions={headers:{Accept:'application/json'}}return(<Providerurl='https://url.com'options={options}><Todos/></Provider>}
Retries retryOn & retryDelay
In this example you can see howretryOn will retry on a status code of305, or if we choose theretryOn() function, it returns a boolean to decide if we will retry. WithretryDelay we can either have a fixed delay, or a dynamic one by usingretryDelay(). Make sureretries is set to at minimum1 otherwise it won't retry the request. Ifretries > 0 withoutretryOn then by default we always retry if there's an error or if!response.ok. IfretryOn: [400] andretries > 0 then we only retry on a response status of400.
importuseFetchfrom'use-http'constTestRetry=()=>{const{ response, get}=useFetch('https://httpbin.org/status/305',{// make sure `retries` is set otherwise it won't retryretries:1,retryOn:[305],// ORretryOn:async({ attempt, error, response})=>{// returns true or false to determine whether to retryreturnerror||response&&response.status>=300},retryDelay:3000,// ORretryDelay:({ attempt, error, response})=>{// exponential backoffreturnMath.min(attempt>1 ?2**attempt*1000 :1000,30*1000)// linear backoffreturnattempt*1000}})return(<><buttononClick={()=>get()}>CLICK</button><pre>{JSON.stringify(response,null,2)}</pre></>)}
| Hook | Description |
|---|---|
useFetch | The base hook |
useQuery | For making a GraphQL query |
useMutation | For making a GraphQL mutation |
This is exactly what you would pass to the normal jsfetch, with a little extra. All these options can be passed to the<Provider options={/* every option below */} />, or directly touseFetch. If you have both in the<Provider /> and inuseFetch, theuseFetch 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'sfetch policies. Possible values arecache-and-network,network-only,cache-only,no-cache,cache-first. Currently only supportscache-first orno-cache | cache-first |
data | Allows you to set a default value fordata | 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 forloading | false unless the last argument ofuseFetch 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 ofcacheLife. IfcacheLife 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 thedata field is set. If you putjson then it will try to parse it as JSON. If you set it as an array, it will attempt to parse theresponse in the order of the types you put in the array. Read about why we don't putformData in the defaultsin 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 seconds30000 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 sureretries > 0 otherwise it won't retry. | [] |
suspense | Enables Experimental React Suspense mode.example | false |
timeout | The request will be aborted/cancelled after this amount of time. This is also the interval at whichretries will be made at.in milliseconds. If set to0, it will not timeout except for browser defaults. | 0 |
constoptions={// 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 requiredreturnoptions// returning the `options` is important},response:async({ response, request})=>{// notes:// - `response.data` is equivalent to `await response.json()`// - `request` is an object matching the standard fetch's optionsreturnresponse// 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 backoffreturnattempt*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 experimental React Suspense modesuspense:true,// defaults to `false`// amount of time before the request get's canceled/abortedtimeout:10000,}useFetch(options)// OR<Provideroptions={options}><ResOfYourApp/></Provider>
Does your company use use-http? Consider sponsoring the project to fund new features, bug fixes, and more.
If you need support for IE, you will need to add additional polyfills. The React docs suggestthese polyfills, but fromthis issue we have found it to work fine with thereact-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 |
If you have feature requests,submit an issue to let us know what you would like to see!
prefetching
global cache state management
optimistic updates
persistsupport for React Nativebetter 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 falseon last request.is making agitpod useful here? 🤔
suspense
- triggering it from outside the
<Suspense />component.- add
.read()torequest - or make it work with just the
suspense: trueoption - 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 translationslike this one
maybe add contributorsall-contributors
add sponsorssimilar to this
Error handling
- if calling
response.json()and there is no response yet
- if calling
tests
- tests for SSR
- tests for react nativesee here
- tests for GraphQL hooks
useMutation+useQuery - tests for stale
responsesee thisPR - tests to make sure
response.formData()and some of the other httpresponse methodswork properly - the
onMountworks properly with all variants of passinguseEffect(fn, [request.get])and not causing an infinite loop asynctests forinterceptors.response- aborts fetch on unmount
- does not abort fetch on every rerender
retryDelayandtimeoutare both set. It works, but is annoying to deal with timers in tests.resourcetimeoutwithretries > 0. (also doretires > 1) Need to figure out how to advance timers properly to write this and the test above
take a look at howreact-apollo-hooks work. Maybe ad
useSubscriptionandconst request = useFetch(); request.subscribe()or something along those linesmake 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
constrequest=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 neededrequest.cache.clear(true)
potential option ideas for
GraphQLconstrequest=useQuery({onMount:true})`your graphql query`constrequest=useFetch(...)constuserID='some-user-uuid'constres=awaitrequest.query({ userID})` query Todos($userID string!) { todos(userID: $userID) { id title } }`
make code editor plugin/package/extension that adds GraphQL syntax highlighting for
useQueryanduseMutation😊add React Native test suite
About
🐶 React hook for making isomorphic http requests
Topics
Resources
License
Contributing
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Sponsor this project
Uh oh!
There was an error while loading.Please reload this page.











