useFetch

React hook for making isomorphic http requests

README

[![use-http logo][3]][5]


useFetch

undefined

    🐶 React hook for making isomorphic http requests

Main Documentation


    
npm i use-http



Features

- SSR (server side rendering) support
- TypeScript support
- 2 dependencies (use-ssr, urs)
- GraphQL support (queries + mutations)
- Provider to set default url and options
- 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

Usage

Examples + Videos


- useFetch - managed state, request, response, etc. undefined undefined
- useFetch - request/response interceptors undefined undefined
- useFetch - retries, retryOn, retryDelay undefined undefined
- useFetch - abort, timeout, onAbort, onTimeout undefined
- useFetch - persist, cache undefined
- useFetch - cacheLife, cachePolicy undefined
- useFetch - suspense (experimental) [![](https://img.shields.io/badge/example-blue.svg)](https://codesandbox.io/s/usefetch-suspense-i22wv) [![](https://img.shields.io/badge/video-red.svg)](https://www.youtube.com/watch?v=7qWLJUpnxHI&list=PLZIwrWkE9rCdUybd8t3tY-mUMvXkCdenW&index=2&t=0s)
- useFetch - pagination undefined undefined
- useQuery - GraphQL undefined
- useFetch - Next.js undefined
- useFetch - create-react-app undefined

Basic Usage Managed State 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.

  1. ``` js
  2. import useFetch from 'use-http'

  3. function Todos() {
  4.   const [todos, setTodos] = useState([])
  5.   const { get, post, response, loading, error } = useFetch('https://example.com')

  6.   useEffect(() => { initializeTodos() }, []) // componentDidMount
  7.   
  8.   async function initializeTodos() {
  9.     const initialTodos = await get('/todos')
  10.     if (response.ok) setTodos(initialTodos)
  11.   }

  12.   async function addTodo() {
  13.     const newTodo = await post('/todos', { title: 'my new todo' })
  14.     if (response.ok) setTodos([...todos, newTodo])
  15.   }

  16.   return (
  17.     <>
  18.       <button onClick={addTodo}>Add Todo</button>
  19.       {error && 'Error!'}
  20.       {loading && 'Loading...'}
  21.       {todos.map(todo => (
  22.         <div key={todo.id}>{todo.title}</div>
  23.       ))}
  24.     </>
  25.   )
  26. }
  27. ```

Basic Usage Auto-Managed State useFetch

This fetch is run onMount/componentDidMount. The last argument [] means it will run onMount. If you pass it a variable like [someVariable], it will run onMount and again whenever someVariable changes values (aka onUpdate). If no method is specified, GET is the default.

  1. ``` js
  2. import useFetch from 'use-http'

  3. function Todos() {
  4.   const options = {} // these options accept all native `fetch` options
  5.   // the last argument below [] means it will fire onMount (GET by default)
  6.   const { loading, error, data = [] } = useFetch('https://example.com/todos', options, [])
  7.   return (
  8.     <>
  9.       {error && 'Error!'}
  10.       {loading && 'Loading...'}
  11.       {data.map(todo => (
  12.         <div key={todo.id}>{todo.title}</div>
  13.       ))}
  14.     </>
  15.   )
  16. }
  17. ```




Suspense Mode(experimental) Auto-Managed State

Can put suspense in 2 places. Either useFetch (A) or Provider (B).

  1. ``` js
  2. import useFetch, { Provider } from 'use-http'

  3. function Todos() {
  4.   const { data: todos = [] } = useFetch('/todos', {
  5.     suspense: true // A. can put `suspense: true` here
  6.   }, []) // onMount

  7.   return todos.map(todo => <div key={todo.id}>{todo.title}</div>)
  8. }

  9. function App() {
  10.   const options = {
  11.     suspense: true // B. can put `suspense: true` here too
  12.   }
  13.   return (
  14.     <Provider url='https://example.com' options={options}>
  15.       <Suspense fallback='Loading...'>
  16.         <Todos />
  17.       </Suspense>
  18.     </Provider>
  19.   )
  20. }
  21. ```

Suspense Mode(experimental) Managed State

Can put suspense in 2 places. Either useFetch (A) or Provider (B). Suspense mode via managed state is very experimental.

  1. ``` js
  2. import useFetch, { Provider } from 'use-http'

  3. function Todos() {
  4.   const [todos, setTodos] = useState([])
  5.   // A. can put `suspense: true` here
  6.   const { get, response } = useFetch({ suspense: true })

  7.   const loadInitialTodos = async () => {
  8.     const todos = await get('/todos')
  9.     if (response.ok) setTodos(todos)
  10.   }

  11.   // componentDidMount
  12.   useEffect(() => {
  13.     loadInitialTodos()
  14.   }, [])

  15.   return todos.map(todo => <div key={todo.id}>{todo.title}</div>)
  16. }

  17. function App() {
  18.   const options = {
  19.     suspense: true // B. can put `suspense: true` here too
  20.   }
  21.   return (
  22.     <Provider url='https://example.com' options={options}>
  23.       <Suspense fallback='Loading...'>
  24.         <Todos />
  25.       </Suspense>
  26.     </Provider>
  27.   )
  28. }
  29. ```




Consider sponsoring

Ava
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

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.

  1. ``` js
  2. import useFetch, { Provider } from 'use-http'

  3. const Todos = () => {
  4.   const [page, setPage] = useState(1)

  5.   const { data = [], loading } = useFetch(`/todos?page=${page}&amountPerPage=15`, {
  6.     onNewData: (currTodos, newTodos) => [...currTodos, ...newTodos], // appends newly fetched todos
  7.     perPage: 15, // stops making more requests if last todos fetched < 15
  8.   }, [page]) // runs onMount AND whenever the `page` updates (onUpdate)

  9.   return (
  10.     <ul>
  11.       {data.map(todo => <li key={todo.id}>{todo.title}</li>}
  12.       {loading && 'Loading...'}
  13.       {!loading && (
  14.         <button onClick={() => setPage(page + 1)}>Load More Todos</button>
  15.       )}
  16.     </ul>
  17.   )
  18. }

  19. const App = () => (
  20.   <Provider url='https://example.com'>
  21.     <Todos />
  22.   </Provider>
  23. )
  24. ```

Destructured useFetch

⚠️ Do not destructure the response object! Details in this 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 for ok where it will be correct for response.ok.  ️️⚠️

  1. ``` js
  2. var [request, response, loading, error] = useFetch('https://example.com')

  3. // want to use object destructuring? You can do that too
  4. var {
  5.   request,
  6.   response, // 🚨 Do not destructure the `response` object!
  7.   loading,
  8.   error,
  9.   data,
  10.   cache,    // methods: get, set, has, delete, clear (like `new Map()`)
  11.   get,
  12.   post,
  13.   put,
  14.   patch,
  15.   delete    // don't destructure `delete` though, it's a keyword
  16.   del,      // <- that's why we have this (del). or use `request.delete`
  17.   head,
  18.   options,
  19.   connect,
  20.   trace,
  21.   mutate,   // GraphQL
  22.   query,    // GraphQL
  23.   abort
  24. } = useFetch('https://example.com')

  25. // 🚨 Do not destructure the `response` object!
  26. // 🚨 This just shows what fields are available in it.
  27. var {
  28.   ok,
  29.   status,
  30.   headers,
  31.   data,
  32.   type,
  33.   statusText,
  34.   url,
  35.   body,
  36.   bodyUsed,
  37.   redirected,
  38.   // methods
  39.   json,
  40.   text,
  41.   formData,
  42.   blob,
  43.   arrayBuffer,
  44.   clone
  45. } = response

  46. var {
  47.   loading,
  48.   error,
  49.   data,
  50.   cache,  // methods: get, set, has, delete, clear (like `new Map()`)
  51.   get,
  52.   post,
  53.   put,
  54.   patch,
  55.   delete  // don't destructure `delete` though, it's a keyword
  56.   del,    // <- that's why we have this (del). or use `request.delete`
  57.   mutate, // GraphQL
  58.   query,  // GraphQL
  59.   abort
  60. } = request
  61. ```

Relative routes useFetch

  1. ``` js
  2. var request = useFetch('https://example.com')

  3. request.post('/todos', {
  4.   no: 'way'
  5. })
  6. ```

Abort useFetch


  1. ``` js
  2. const { get, abort, loading, data: repos } = useFetch('https://api.github.com/search/repositories?q=')

  3. // the line below is not isomorphic, but for simplicity we're using the browsers `encodeURI`
  4. const searchGithubRepos = e => get(encodeURI(e.target.value))

  5. <>
  6.   <input onChange={searchGithubRepos} />
  7.   <button onClick={abort}>Abort</button>
  8.   {loading ? 'Loading...' : repos.data.items.map(repo => (
  9.     <div key={repo.id}>{repo.name}</div>
  10.   ))}
  11. </>
  12. ```

GraphQL Query useFetch

  1. ``` js

  2. const QUERY = `
  3.   query Todos($userID string!) {
  4.     todos(userID: $userID) {
  5.       id
  6.       title
  7.     }
  8.   }
  9. `

  10. function App() {
  11.   const request = useFetch('http://example.com')

  12.   const getTodosForUser = id => request.query(QUERY, { userID: id })

  13.   return (
  14.     <>
  15.       <button onClick={() => getTodosForUser('theUsersID')}>Get User's Todos</button>
  16.       {request.loading ? 'Loading...' : <pre>{request.data}</pre>}
  17.     </>
  18.   )
  19. }
  20. ```
GraphQL Mutation useFetch

The Provider allows us to set a default url, options (such as headers) and so on.

  1. ``` js

  2. const MUTATION = `
  3.   mutation CreateTodo($todoTitle string) {
  4.     todo(title: $todoTitle) {
  5.       id
  6.       title
  7.     }
  8.   }
  9. `

  10. function App() {
  11.   const [todoTitle, setTodoTitle] = useState('')
  12.   const request = useFetch('http://example.com')

  13.   const createtodo = () => request.mutate(MUTATION, { todoTitle })

  14.   return (
  15.     <>
  16.       <input onChange={e => setTodoTitle(e.target.value)} />
  17.       <button onClick={createTodo}>Create Todo</button>
  18.       {request.loading ? 'Loading...' : <pre>{request.data}</pre>}
  19.     </>
  20.   )
  21. }
  22. ```
Provider using the GraphQL useMutation and useQuery

Query for todos
  1. ``` js
  2. import { useQuery } from 'use-http'

  3. export default function QueryComponent() {
  4.   
  5.   // can also do it this way:
  6.   // const [data, loading, error, query] = useQuery`
  7.   // or this way:
  8.   // const { data, loading, error, query } = useQuery`
  9.   const request = useQuery`
  10.     query Todos($userID string!) {
  11.       todos(userID: $userID) {
  12.         id
  13.         title
  14.       }
  15.     }
  16.   `

  17.   const getTodosForUser = id => request.query({ userID: id })
  18.   
  19.   return (
  20.     <>
  21.       <button onClick={() => getTodosForUser('theUsersID')}>Get User's Todos</button>
  22.       {request.loading ? 'Loading...' : <pre>{request.data}</pre>}
  23.     </>
  24.   )
  25. }
  26. ```



Add a new todo
  1. ``` js
  2. import { useMutation } from 'use-http'

  3. export default function MutationComponent() {
  4.   const [todoTitle, setTodoTitle] = useState('')
  5.   
  6.   // can also do it this way:
  7.   // const request = useMutation`
  8.   // or this way:
  9.   // const { data, loading, error, mutate } = useMutation`
  10.   const [data, loading, error, mutate] = useMutation`
  11.     mutation CreateTodo($todoTitle string) {
  12.       todo(title: $todoTitle) {
  13.         id
  14.         title
  15.       }
  16.     }
  17.   `
  18.   
  19.   const createTodo = () => mutate({ todoTitle })

  20.   return (
  21.     <>
  22.       <input onChange={e => setTodoTitle(e.target.value)} />
  23.       <button onClick={createTodo}>Create Todo</button>
  24.       {loading ? 'Loading...' : <pre>{data}</pre>}
  25.     </>
  26.   )
  27. }
  28. ```


Adding the Provider
These props are defaults used in every request inside the ``. They can be overwritten individually
  1. ``` js
  2. import { Provider } from 'use-http'
  3. import QueryComponent from './QueryComponent'
  4. import MutationComponent from './MutationComponent'

  5. function App() {

  6.   const options = {
  7.     headers: {
  8.       Authorization: 'Bearer YOUR_TOKEN_HERE'
  9.     }
  10.   }
  11.   
  12.   return (
  13.     <Provider url='http://example.com' options={options}>
  14.       <QueryComponent />
  15.       <MutationComponent />
  16.     <Provider/>
  17.   )
  18. }

  19. ```
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
    
  1. ``` js
  2. import { Provider } from 'use-http'
  3. import { toCamel } from 'convert-keys'

  4. function App() {
  5.   let [token, setToken] = useLocalStorage('token')
  6.   
  7.   const options = {
  8.     interceptors: {
  9.       // every time we make an http request, this will run 1st before the request is made
  10.       // url, path and route are supplied to the interceptor
  11.       // request options can be modified and must be returned
  12.       request: async ({ options, url, path, route }) => {
  13.         if (isExpired(token)) {
  14.           token = await getNewToken()
  15.           setToken(token)
  16.         }
  17.         options.headers.Authorization = `Bearer ${token}`
  18.         return options
  19.       },
  20.       // every time we make an http request, before getting the response back, this will run
  21.       response: async ({ response, request }) => {
  22.         // unfortunately, because this is a JS Response object, we have to modify it directly.
  23.         // It shouldn't have any negative affect since this is getting reset on each request.
  24.         const res = response
  25.         if (res.data) res.data = toCamel(res.data)
  26.         return res
  27.       }
  28.     }
  29.   }
  30.   
  31.   return (
  32.     <Provider url='http://example.com' options={options}>
  33.       <SomeComponent />
  34.     <Provider/>
  35.   )
  36. }

  37. ```

File Uploads (FormData)
    
This example shows how we can upload a file using useFetch.
    
  1. ``` js
  2. import useFetch from 'use-http'

  3. const FileUploader = () => {
  4.   const [file, setFile] = useState()
  5.   
  6.   const { post } = useFetch('https://example.com/upload')

  7.   const uploadFile = async () => {
  8.     const data = new FormData()
  9.     data.append('file', file)
  10.     if (file instanceof FormData) await post(data)
  11.   }

  12.   return (
  13.     <div>
  14.       {/* Drop a file onto the input below */}
  15.       <input onChange={e => setFile(e.target.files[0])} />
  16.       <button onClick={uploadFile}>Upload</button>
  17.     </div>
  18.   )
  19. }
  20. ```
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.

  1. ``` js
  2. import useFetch from 'use-http'

  3. const App = () => {
  4.   const [name, setName] = useState('')
  5.   
  6.   const { get, loading, error, response } = useFetch('http://example.com')

  7.   const handleClick = async () => {
  8.     await get('/users/1?name=true') // will return just the user's name
  9.     const text = await response.text()
  10.     setName(text)
  11.   }
  12.   
  13.   return (
  14.     <>
  15.       <button onClick={handleClick}>Load Data</button>
  16.       {error && error.messge}
  17.       {loading && "Loading..."}
  18.       {name && <div>{name}</div>}
  19.     </>
  20.   )
  21. }
  22. ```



Overwrite/Remove Options/Headers Set in Provider
    
This example shows how to remove a header all together. Let's say you have ``, 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.

  1. ``` js
  2. import useFetch from 'use-http'

  3. const Todos = () => {
  4.   // let's say for this request, you don't want the `Accept` header at all
  5.   const { loading, error, data: todos = [] } = useFetch('/todos', globalOptions => {
  6.     delete globalOptions.headers.Accept
  7.     return globalOptions
  8.   }, []) // onMount
  9.   return (
  10.     <>
  11.       {error && error.messge}
  12.       {loading && "Loading..."}
  13.       {todos && <ul>{todos.map(todo => <li key={todo.id}>{todo.title}</li>)}ul>}
  14.     </>
  15.   )
  16. }

  17. const App = () => {
  18.   const options = {
  19.     headers: {
  20.       Accept: 'application/json'
  21.     }
  22.   }
  23.   return (
  24.     <Provider url='https://url.com' options={options}><Todos />Provider>
  25. }
  26. ```

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.

  1. ``` js
  2. import useFetch from 'use-http'

  3. const TestRetry = () => {
  4.   const { response, get } = useFetch('https://httpbin.org/status/305', {
  5.     // make sure `retries` is set otherwise it won't retry
  6.     retries: 1,
  7.     retryOn: [305],
  8.     // OR
  9.     retryOn: async ({ attempt, error, response }) => {
  10.       // returns true or false to determine whether to retry
  11.       return error || response && response.status >= 300
  12.     },

  13.     retryDelay: 3000,
  14.     // OR
  15.     retryDelay: ({ attempt, error, response }) => {
  16.       // exponential backoff
  17.       return Math.min(attempt > 1 ? 2 ** attempt * 1000 : 1000, 30 * 1000)
  18.       // linear backoff
  19.       return attempt * 1000
  20.     }
  21.   })

  22.   return (
  23.     <>
  24.       <button onClick={() => get()}>CLICK</button>
  25.       <pre>{JSON.stringify(response, null, 2)}</pre>
  26.     </>
  27.   )
  28. }
  29. ```


Overview

Hooks


HookDescription
-------------------------------------------------------------------------------------------------------------
`useFetch`The
`useQuery`For
`useMutation`For
    


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 ``, or directly to `useFetch`. If you have both in the `` and in `useFetch`, the `useFetch` options will overwrite the ones from the ``

OptionDescriptionDefault
-----------------------------------------------------------------------------------------------|-------------
`cacheLife`After`0`
`cachePolicy`These`cache-first`
`data`Allows`undefined`
`interceptors.request`Allows`undefined`
`interceptors.response`Allows`undefined`
`loading`Allows`false`
`onAbort`Runsempty
`onError`Runsempty
`onNewData`Merges`(curr,
`onTimeout`Calledempty
`persist`Persists`false`
`perPage`Stops`0`
`responseType`This`['json',
`retries`When`0`
`retryDelay`You`1000`
`retryOn`You`[]`
`suspense`Enables`false`
`timeout`The`0`

  1. ``` js
  2. const options = {
  3.   // accepts all `fetch` options such as headers, method, etc.

  4.   // The time in milliseconds that cache data remains fresh.
  5.   cacheLife: 0,

  6.   // Cache responses to improve speed and reduce amount of requests
  7.   // Only one request to the same endpoint will be initiated unless cacheLife expires for 'cache-first'.
  8.   cachePolicy: 'cache-first', // 'no-cache'
  9.   
  10.   // set's the default for the `data` field
  11.   data: [],

  12.   // typically, `interceptors` would be added as an option to the ``
  13.   interceptors: {
  14.     request: async ({ options, url, path, route }) => { // `async` is not required
  15.       return options // returning the `options` is important
  16.     },
  17.     response: async ({ response, request }) => {
  18.       // notes:
  19.       // - `response.data` is equivalent to `await response.json()`
  20.       // - `request` is an object matching the standard fetch's options
  21.       return response // returning the `response` is important
  22.     }
  23.   },

  24.   // set's the default for `loading` field
  25.   loading: false,
  26.   
  27.   // called when aborting the request
  28.   onAbort: () => {},
  29.   
  30.   // runs when an error happens.
  31.   onError: ({ error }) => {},

  32.   // this will allow you to merge the `data` for pagination.
  33.   onNewData: (currData, newData) => {
  34.     return [...currData, ...newData]
  35.   },
  36.   
  37.   // called when the request times out
  38.   onTimeout: () => {},
  39.   
  40.   // this will tell useFetch not to run the request if the list doesn't haveMore. (pagination)
  41.   // i.e. if the last page fetched was < 15, don't run the request again
  42.   perPage: 15,

  43.   // Allows caching to persist after page refresh. Only supported in the Browser currently.
  44.   persist: false,

  45.   // this would basically call `await response.json()`
  46.   // and set the `data` and `response.data` field to the output
  47.   responseType: 'json',
  48.   // OR can be an array. It's an array by default.
  49.   // We will try to get the `data` by attempting to extract
  50.   // it via these body interface methods, one by one in
  51.   // this order. We skip `formData` because it's mostly used
  52.   // for service workers.
  53.   responseType: ['json', 'text', 'blob', 'arrayBuffer'],

  54.   // amount of times it should retry before erroring out
  55.   retries: 3,

  56.   // The time between retries
  57.   retryDelay: 10000,
  58.   // OR
  59.   // Can be a function which is used if we want change the time in between each retry
  60.   retryDelay({ attempt, error, response }) {
  61.     // exponential backoff
  62.     return Math.min(attempt > 1 ? 2 ** attempt * 1000 : 1000, 30 * 1000)
  63.     // linear backoff
  64.     return attempt * 1000
  65.   },

  66.   // make sure `retries` is set otherwise it won't retry
  67.   // can retry on certain http status codes
  68.   retryOn: [503],
  69.   // OR
  70.   async retryOn({ attempt, error, response }) {
  71.     // retry on any network error, or 4xx or 5xx status codes
  72.     if (error !== null || response.status >= 400) {
  73.       console.log(`retrying, attempt number ${attempt + 1}`);
  74.       return true;
  75.     }
  76.   },

  77.   // enables experimental React Suspense mode
  78.   suspense: true, // defaults to `false`
  79.   
  80.   // amount of time before the request get's canceled/aborted
  81.   timeout: 10000,
  82. }

  83. useFetch(options)
  84. // OR
  85. <Provider options={options}><ResOfYourApp />Provider>
  86. ```

Who's using use-http?

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][4], but from [this issue][2] 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!

[[[[[
---------------------------------------------
12+lastlastlastlast

Feature Requests/Ideas

If you have feature requests, [submit an issue][1] 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 `` component.
    - add .read() to request
    - or make it work with just the suspense: true option
    - both of these options need to be thought out a lot more^
  - [ ] 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
- [ ] 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
- [ ] 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 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 aduseSubscription and const 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

  1. ``` js
  2.   const request = useFetch({
  3.     graphql: {
  4.       // all options can also be put in here
  5.       // to overwrite those of `useFetch` for
  6.       // `useMutation` and `useQuery`
  7.     },
  8.     // by default this is true, but if set to false
  9.     // then we default to the responseType array of trying 'json' first, then 'text', etc.
  10.     // hopefully I get some answers on here: https://bit.ly/3afPlJS
  11.     responseTypeGuessing: true,

  12.     // Allows you to pass in your own cache to useFetch
  13.     // This is controversial though because `cache` is an option in the requestInit
  14.     // and it's value is a string. See: https://developer.mozilla.org/en-US/docs/Web/API/Request/cache
  15.     // One possible solution is to move the default `fetch`'s `cache` to `cachePolicy`.
  16.     // I don't really like this solution though.
  17.     // Another solution is to only allow the `cache` option with the ``
  18.     cache: new Map(),
  19.     // these will be the exact same ones as Apollo's
  20.     cachePolicy: 'cache-and-network', 'network-only', 'cache-only', 'no-cache' // 'cache-first'
  21.     // potential idea to fetch on server instead of just having `loading` state. Not sure if this is a good idea though
  22.     onServer: true,
  23.     onSuccess: (/* idk what to put here */) => {},
  24.     // if you would prefer to pass the query in the config
  25.     query: `some graphql query`
  26.     // if you would prefer to pass the mutation in the config
  27.     mutation: `some graphql mutation`
  28.     refreshWhenHidden: false,
  29.   })


  30.   // potential for causing a rerender after clearing cache if needed
  31.   request.cache.clear(true)
  32. ```

- [ ] potential option ideas for GraphQL

  1. ``` js
  2.   const request = useQuery({ onMount: true })`your graphql query`

  3.   const request = useFetch(...)
  4.   const userID = 'some-user-uuid'
  5.   const res = await request.query({ userID })`
  6.     query Todos($userID string!) {
  7.       todos(userID: $userID) {
  8.         id
  9.         title
  10.       }
  11.     }
  12.   `
  13. ```

- [ ] make code editor plugin/package/extension that adds GraphQL syntax highlighting for useQuery and useMutation 😊

- [ ] add React Native test suite

[1]: https://github.com/ava/use-http/issues/new?title=[Feature%20Request]%20YOUR_FEATURE_NAME
[2]: https://github.com/ava/use-http/issues/93#issuecomment-600896722
[3]: https://github.com/ava/use-http/raw/master/public/dog.png
[4]: https://reactjs.org/docs/javascript-environment-requirements.html
[5]: https://use-http.com
[react-app-polyfill]: https://www.npmjs.com/package/react-app-polyfill