66 |
67 |
68 |
69 |
70 | Features
71 | ---------
72 |
73 | - SSR (server side rendering) support
74 | - TypeScript support
75 | - 2 dependencies ([use-ssr](https://github.com/alex-cory/use-ssr), [urs](https://github.com/alex-cory/urs))
76 | - GraphQL support (queries + mutations)
77 | - Provider to set default `url` and `options`
78 | - Request/response interceptors
79 | - React Native support
80 | - Aborts/Cancels pending http requests when a component unmounts
81 | - Built in caching
82 | - Persistent caching support
83 | - Suspense(experimental) support
84 | - Retry functionality
85 |
86 | Usage
87 | -----
88 |
89 | ### Examples + Videos
90 |
91 | - useFetch - managed state, request, response, etc. [](https://codesandbox.io/s/usefetch-request-response-managed-state-ruyi3?file=/src/index.js) [](https://www.youtube.com/watch?v=_-GujYZFCKI&list=PLZIwrWkE9rCdUybd8t3tY-mUMvXkCdenW&index=6)
92 | - useFetch - request/response interceptors [](https://codesandbox.io/s/usefetch-provider-requestresponse-interceptors-s1lex) [](https://www.youtube.com/watch?v=3HauoWh0Jts&list=PLZIwrWkE9rCdUybd8t3tY-mUMvXkCdenW&index=8)
93 | - useFetch - retries, retryOn, retryDelay [](https://codesandbox.io/s/usefetch-retryon-retrydelay-s74q9) [](https://www.youtube.com/watch?v=grE3AX-Q9ss&list=PLZIwrWkE9rCdUybd8t3tY-mUMvXkCdenW&index=9)
94 | - useFetch - abort, timeout, onAbort, onTimeout [](https://www.youtube.com/watch?v=7SuD3ZOfu7E&list=PLZIwrWkE9rCdUybd8t3tY-mUMvXkCdenW&index=4)
95 | - useFetch - persist, cache [](https://www.youtube.com/watch?v=pJ22Rq9c8mw&list=PLZIwrWkE9rCdUybd8t3tY-mUMvXkCdenW&index=7)
96 | - useFetch - cacheLife, cachePolicy [](https://www.youtube.com/watch?v=AsZ9hnWHCeg&list=PLZIwrWkE9rCdUybd8t3tY-mUMvXkCdenW&index=3&t=0s)
97 | - useFetch - suspense (experimental) [](https://codesandbox.io/s/usefetch-suspense-i22wv) [](https://www.youtube.com/watch?v=7qWLJUpnxHI&list=PLZIwrWkE9rCdUybd8t3tY-mUMvXkCdenW&index=2&t=0s)
98 | - useFetch - pagination [](https://codesandbox.io/s/usefetch-provider-pagination-exttg) [](https://www.youtube.com/watch?v=YmcMjRpIYqU&list=PLZIwrWkE9rCdUybd8t3tY-mUMvXkCdenW&index=5)
99 | - useQuery - GraphQL [](https://codesandbox.io/s/graphql-usequery-provider-uhdmj)
100 | - useFetch - Next.js [](https://codesandbox.io/s/usefetch-in-nextjs-nn9fm)
101 | - useFetch - create-react-app [](https://codesandbox.io/embed/km04k9k9x5)
102 |
103 | Basic Usage Managed State useFetch
104 |
105 | 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.
106 |
107 | ```js
108 | import useFetch from 'use-http'
109 |
110 | function Todos() {
111 | const [todos, setTodos] = useState([])
112 | const { get, post, response, loading, error } = useFetch('https://example.com')
113 |
114 | useEffect(() => { initializeTodos() }, []) // componentDidMount
115 |
116 | async function initializeTodos() {
117 | const initialTodos = await get('/todos')
118 | if (response.ok) setTodos(initialTodos)
119 | }
120 |
121 | async function addTodo() {
122 | const newTodo = await post('/todos', { title: 'my new todo' })
123 | if (response.ok) setTodos([...todos, newTodo])
124 | }
125 |
126 | return (
127 | <>
128 |
129 | {error && 'Error!'}
130 | {loading && 'Loading...'}
131 | {todos.map(todo => (
132 |
{todo.title}
133 | ))}
134 | >
135 | )
136 | }
137 | ```
138 |
139 |
140 |
141 |
142 |
143 | Basic Usage Auto-Managed State useFetch
144 |
145 | 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.
146 |
147 | ```js
148 | import useFetch from 'use-http'
149 |
150 | function Todos() {
151 | const options = {} // these options accept all native `fetch` options
152 | // the last argument below [] means it will fire onMount (GET by default)
153 | const { loading, error, data = [] } = useFetch('https://example.com/todos', options, [])
154 | return (
155 | <>
156 | {error && 'Error!'}
157 | {loading && 'Loading...'}
158 | {data.map(todo => (
159 |
{todo.title}
160 | ))}
161 | >
162 | )
163 | }
164 | ```
165 |
166 |
167 |
168 |
169 |
170 |
171 | Suspense Mode(experimental) Auto-Managed State
172 |
173 | Can put `suspense` in 2 places. Either `useFetch` (A) or `Provider` (B).
174 |
175 | ```js
176 | import useFetch, { Provider } from 'use-http'
177 |
178 | function Todos() {
179 | const { data: todos = [] } = useFetch('/todos', {
180 | suspense: true // A. can put `suspense: true` here
181 | }, []) // onMount
182 |
183 | return todos.map(todo =>
{todo.title}
)
184 | }
185 |
186 | function App() {
187 | const options = {
188 | suspense: true // B. can put `suspense: true` here too
189 | }
190 | return (
191 |
192 |
193 |
194 |
195 |
196 | )
197 | }
198 | ```
199 |
200 |
201 |
202 |
203 |
204 | Suspense Mode(experimental) Managed State
205 |
206 | Can put `suspense` in 2 places. Either `useFetch` (A) or `Provider` (B). Suspense mode via managed state is very experimental.
207 |
208 | ```js
209 | import useFetch, { Provider } from 'use-http'
210 |
211 | function Todos() {
212 | const [todos, setTodos] = useState([])
213 | // A. can put `suspense: true` here
214 | const { get, response } = useFetch({ suspense: true })
215 |
216 | const loadInitialTodos = async () => {
217 | const todos = await get('/todos')
218 | if (response.ok) setTodos(todos)
219 | }
220 |
221 | // componentDidMount
222 | useEffect(() => {
223 | loadInitialTodos()
224 | }, [])
225 |
226 | return todos.map(todo =>
250 |
251 | Consider sponsoring
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 | Ava, Rapid Application Development
260 |
261 |
262 | Need a freelance software engineer with more than 5 years production experience at companies like Facebook, Discord, Best Buy, and Citrix?
263 | website | email | twitter
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 | Pagination + Provider
273 |
274 | 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.
275 |
276 | ```jsx
277 | import useFetch, { Provider } from 'use-http'
278 |
279 | const Todos = () => {
280 | const [page, setPage] = useState(1)
281 |
282 | const { data = [], loading } = useFetch(`/todos?page=${page}&amountPerPage=15`, {
283 | onNewData: (currTodos, newTodos) => [...currTodos, ...newTodos], // appends newly fetched todos
284 | perPage: 15, // stops making more requests if last todos fetched < 15
285 | }, [page]) // runs onMount AND whenever the `page` updates (onUpdate)
286 |
287 | return (
288 |
}
547 | >
548 | )
549 | }
550 | ```
551 |
552 |
553 | ##### Adding the Provider
554 | These props are defaults used in every request inside the ``. They can be overwritten individually
555 | ```jsx
556 | import { Provider } from 'use-http'
557 | import QueryComponent from './QueryComponent'
558 | import MutationComponent from './MutationComponent'
559 |
560 | function App() {
561 |
562 | const options = {
563 | headers: {
564 | Authorization: 'Bearer YOUR_TOKEN_HERE'
565 | }
566 | }
567 |
568 | return (
569 |
570 |
571 |
572 |
573 | )
574 | }
575 |
576 | ```
577 |
578 |
579 |
580 | Request/Response Interceptors
581 |
582 | This example shows how we can do authentication in the `request` interceptor and how we can camelCase the results in the `response` interceptor
583 |
584 | ```jsx
585 | import { Provider } from 'use-http'
586 | import { toCamel } from 'convert-keys'
587 |
588 | function App() {
589 | let [token, setToken] = useLocalStorage('token')
590 |
591 | const options = {
592 | interceptors: {
593 | // every time we make an http request, this will run 1st before the request is made
594 | // url, path and route are supplied to the interceptor
595 | // request options can be modified and must be returned
596 | request: async ({ options, url, path, route }) => {
597 | if (isExpired(token)) {
598 | token = await getNewToken()
599 | setToken(token)
600 | }
601 | options.headers.Authorization = `Bearer ${token}`
602 | return options
603 | },
604 | // every time we make an http request, before getting the response back, this will run
605 | response: async ({ response, request }) => {
606 | // unfortunately, because this is a JS Response object, we have to modify it directly.
607 | // It shouldn't have any negative affect since this is getting reset on each request.
608 | const res = response
609 | if (res.data) res.data = toCamel(res.data)
610 | return res
611 | }
612 | }
613 | }
614 |
615 | return (
616 |
617 |
618 |
619 | )
620 | }
621 |
622 | ```
623 |
624 |
625 |
626 |
627 |
628 | File Uploads (FormData)
629 |
630 | This example shows how we can upload a file using `useFetch`.
631 |
632 | ```jsx
633 | import useFetch from 'use-http'
634 |
635 | const FileUploader = () => {
636 | const [file, setFile] = useState()
637 |
638 | const { post } = useFetch('https://example.com/upload')
639 |
640 | const uploadFile = async () => {
641 | const data = new FormData()
642 | data.append('file', file)
643 | if (file instanceof FormData) await post(data)
644 | }
645 |
646 | return (
647 |
648 | {/* Drop a file onto the input below */}
649 | setFile(e.target.files[0])} />
650 |
651 |
652 | )
653 | }
654 | ```
655 |
656 |
657 | Handling Different Response Types
658 |
659 | This example shows how we can get `.json()`, `.text()`, `.formData()`, `.blob()`, `.arrayBuffer()`, and all the other [http response methods](https://developer.mozilla.org/en-US/docs/Web/API/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.
660 |
661 | ```js
662 | import useFetch from 'use-http'
663 |
664 | const App = () => {
665 | const [name, setName] = useState('')
666 |
667 | const { get, loading, error, response } = useFetch('http://example.com')
668 |
669 | const handleClick = async () => {
670 | await get('/users/1?name=true') // will return just the user's name
671 | const text = await response.text()
672 | setName(text)
673 | }
674 |
675 | return (
676 | <>
677 |
678 | {error && error.messge}
679 | {loading && "Loading..."}
680 | {name &&
{name}
}
681 | >
682 | )
683 | }
684 | ```
685 |
686 |
687 |
688 |
689 |
690 |
691 | Overwrite/Remove Options/Headers Set in Provider
692 |
693 | 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.
694 |
695 | ```js
696 | import useFetch from 'use-http'
697 |
698 | const Todos = () => {
699 | // let's say for this request, you don't want the `Accept` header at all
700 | const { loading, error, data: todos = [] } = useFetch('/todos', globalOptions => {
701 | delete globalOptions.headers.Accept
702 | return globalOptions
703 | }, []) // onMount
704 | return (
705 | <>
706 | {error && error.messge}
707 | {loading && "Loading..."}
708 | {todos &&
{todos.map(todo =>
{todo.title}
)}
}
709 | >
710 | )
711 | }
712 |
713 | const App = () => {
714 | const options = {
715 | headers: {
716 | Accept: 'application/json'
717 | }
718 | }
719 | return (
720 |
721 | }
722 | ```
723 |
724 |
725 |
726 | Retries retryOn & retryDelay
727 |
728 | 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`.
729 |
730 | ```js
731 | import useFetch from 'use-http'
732 |
733 | const TestRetry = () => {
734 | const { response, get } = useFetch('https://httpbin.org/status/305', {
735 | // make sure `retries` is set otherwise it won't retry
736 | retries: 1,
737 | retryOn: [305],
738 | // OR
739 | retryOn: async ({ attempt, error, response }) => {
740 | // returns true or false to determine whether to retry
741 | return error || response && response.status >= 300
742 | },
743 |
744 | retryDelay: 3000,
745 | // OR
746 | retryDelay: ({ attempt, error, response }) => {
747 | // exponential backoff
748 | return Math.min(attempt > 1 ? 2 ** attempt * 1000 : 1000, 30 * 1000)
749 | // linear backoff
750 | return attempt * 1000
751 | }
752 | })
753 |
754 | return (
755 | <>
756 |
757 |
{JSON.stringify(response, null, 2)}
758 | >
759 | )
760 | }
761 | ```
762 |
763 |
764 |
765 |
766 |
767 | Overview
768 | --------
769 |
770 | ### Hooks
771 |
772 | | Hook | Description |
773 | | --------------------- | ---------------------------------------------------------------------------------------- |
774 | | `useFetch` | The base hook |
775 | | `useQuery` | For making a GraphQL query |
776 | | `useMutation` | For making a GraphQL mutation |
777 |
778 |
779 |
780 |
781 | ### Options
782 |
783 | 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 ``
784 |
785 | | Option | Description | Default |
786 | | --------------------- | --------------------------------------------------------------------------|------------- |
787 | | `cacheLife` | After a successful cache update, that cache data will become stale after this duration | `0` |
788 | | `cachePolicy` | These will be the same ones as Apollo's [fetch policies](https://www.apollographql.com/docs/react/api/react/hoc/#optionsfetchpolicy). Possible values are `cache-and-network`, `network-only`, `cache-only`, `no-cache`, `cache-first`. Currently only supports **`cache-first`** or **`no-cache`** | `cache-first` |
789 | | `data` | Allows you to set a default value for `data` | `undefined` |
790 | | `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` |
791 | | `interceptors.response` | Allows you to do something after an http response is recieved. Useful for something like camelCasing the keys of the response. | `undefined` |
792 | | `loading` | Allows you to set default value for `loading` | `false` unless the last argument of `useFetch` is `[]` |
793 | | `onAbort` | Runs when the request is aborted. | empty function |
794 | | `onError` | Runs when the request get's an error. If retrying, it is only called on the last retry attempt. | empty function |
795 | | `onNewData` | Merges the current data with the incoming data. Great for pagination. | `(curr, new) => new` |
796 | | `onTimeout` | Called when the request times out. | empty function |
797 | | `persist` | Persists data for the duration of `cacheLife`. If `cacheLife` is not set it defaults to 24h. Currently only available in Browser. | `false` |
798 | | `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` |
799 | | `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](https://developer.mozilla.org/en-US/docs/Web/API/Body/formData). | `['json', 'text', 'blob', 'readableStream']` |
800 | | `retries` | When a request fails or times out, retry the request this many times. By default it will not retry. | `0` |
801 | | `retryDelay` | You can retry with certain intervals i.e. 30 seconds `30000` or with custom logic (i.e. to increase retry intervals). | `1000` |
802 | | `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. | `[]` |
803 | | `suspense` | Enables Experimental React Suspense mode. [example](https://codesandbox.io/s/usefetch-suspense-i22wv) | `false` |
804 | | `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` |
805 |
806 | ```jsx
807 | const options = {
808 | // accepts all `fetch` options such as headers, method, etc.
809 |
810 | // The time in milliseconds that cache data remains fresh.
811 | cacheLife: 0,
812 |
813 | // Cache responses to improve speed and reduce amount of requests
814 | // Only one request to the same endpoint will be initiated unless cacheLife expires for 'cache-first'.
815 | cachePolicy: 'cache-first', // 'no-cache'
816 |
817 | // set's the default for the `data` field
818 | data: [],
819 |
820 | // typically, `interceptors` would be added as an option to the ``
821 | interceptors: {
822 | request: async ({ options, url, path, route }) => { // `async` is not required
823 | return options // returning the `options` is important
824 | },
825 | response: async ({ response, request }) => {
826 | // notes:
827 | // - `response.data` is equivalent to `await response.json()`
828 | // - `request` is an object matching the standard fetch's options
829 | return response // returning the `response` is important
830 | }
831 | },
832 |
833 | // set's the default for `loading` field
834 | loading: false,
835 |
836 | // called when aborting the request
837 | onAbort: () => {},
838 |
839 | // runs when an error happens.
840 | onError: ({ error }) => {},
841 |
842 | // this will allow you to merge the `data` for pagination.
843 | onNewData: (currData, newData) => {
844 | return [...currData, ...newData]
845 | },
846 |
847 | // called when the request times out
848 | onTimeout: () => {},
849 |
850 | // this will tell useFetch not to run the request if the list doesn't haveMore. (pagination)
851 | // i.e. if the last page fetched was < 15, don't run the request again
852 | perPage: 15,
853 |
854 | // Allows caching to persist after page refresh. Only supported in the Browser currently.
855 | persist: false,
856 |
857 | // this would basically call `await response.json()`
858 | // and set the `data` and `response.data` field to the output
859 | responseType: 'json',
860 | // OR can be an array. It's an array by default.
861 | // We will try to get the `data` by attempting to extract
862 | // it via these body interface methods, one by one in
863 | // this order. We skip `formData` because it's mostly used
864 | // for service workers.
865 | responseType: ['json', 'text', 'blob', 'arrayBuffer'],
866 |
867 | // amount of times it should retry before erroring out
868 | retries: 3,
869 |
870 | // The time between retries
871 | retryDelay: 10000,
872 | // OR
873 | // Can be a function which is used if we want change the time in between each retry
874 | retryDelay({ attempt, error, response }) {
875 | // exponential backoff
876 | return Math.min(attempt > 1 ? 2 ** attempt * 1000 : 1000, 30 * 1000)
877 | // linear backoff
878 | return attempt * 1000
879 | },
880 |
881 | // make sure `retries` is set otherwise it won't retry
882 | // can retry on certain http status codes
883 | retryOn: [503],
884 | // OR
885 | async retryOn({ attempt, error, response }) {
886 | // retry on any network error, or 4xx or 5xx status codes
887 | if (error !== null || response.status >= 400) {
888 | console.log(`retrying, attempt number ${attempt + 1}`);
889 | return true;
890 | }
891 | },
892 |
893 | // enables experimental React Suspense mode
894 | suspense: true, // defaults to `false`
895 |
896 | // amount of time before the request get's canceled/aborted
897 | timeout: 10000,
898 | }
899 |
900 | useFetch(options)
901 | // OR
902 |
903 | ```
904 |
905 | Who's using use-http?
906 | ----------------------
907 |
908 | Does your company use use-http? Consider sponsoring the project to fund new features, bug fixes, and more.
909 |
910 |
929 |
930 | Browser Support
931 | ---------------
932 |
933 | 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!
934 |
935 | | []() Edge | []() Firefox | []() Chrome | []() Safari | []() Opera |
936 | | --------- | --------- | --------- | --------- | --------- |
937 | | 12+ | last 2 versions| last 2 versions| last 2 versions| last 2 versions |
938 |
939 | Feature Requests/Ideas
940 | ----------------------
941 |
942 | If you have feature requests, [submit an issue][1] to let us know what you would like to see!
943 |
944 | Todos
945 | ------
946 | - [ ] [OSS analytics](https://www.npmjs.com/package/@scarf/scarf)
947 | - [ ] [contributors](https://github.com/all-contributors/all-contributors)
948 | - [ ] prefetching
949 | - [ ] global cache state management
950 | - [ ] optimistic updates
951 | - [ ] `persist` support for React Native
952 | - [ ] better loading state management. When using only 1 useFetch in a component and we use
953 | `Promise.all([get('/todos/1'), get('/todos/2')])` then don't have a loading true,
954 | loading false on each request. Just have loading true on 1st request, and loading false
955 | on last request.
956 | - [ ] is making a [gitpod](https://www.gitpod.io/docs/configuration/) useful here? 🤔
957 | - [ ] suspense
958 | - [ ] triggering it from outside the `` component.
959 | - add `.read()` to `request`
960 | - or make it work with just the `suspense: true` option
961 | - both of these options need to be thought out a lot more^
962 | - [ ] tests for this^ (triggering outside)
963 | - [ ] cleanup tests in general. Snapshot tests are unpredictably not working for some reason.
964 | - snapshot test resources: [swr](https://github.com/zeit/swr/blob/master/test/use-swr.test.tsx#L1083), [react-apollo-hooks](https://github.com/trojanowski/react-apollo-hooks/blob/master/src/__tests__/useQuery-test.tsx#L218)
965 | - basic test resources: [fetch-suspense](https://github.com/CharlesStover/fetch-suspense/blob/master/tests/create-use-fetch.test.ts), [@testing-library/react-hooks suspense PR](https://github.com/testing-library/react-hooks-testing-library/pull/35/files)
966 | - [ ] maybe add translations [like this one](https://github.com/jamiebuilds/unstated-next)
967 | - [ ] maybe add contributors [all-contributors](https://github.com/all-contributors/all-contributors)
968 | - [ ] add sponsors [similar to this](https://github.com/carbon-app/carbon)
969 | - [ ] Error handling
970 | - [ ] if calling `response.json()` and there is no response yet
971 | - [ ] tests
972 | - [ ] tests for SSR
973 | - [ ] tests for react native [see here](https://stackoverflow.com/questions/45842088/react-native-mocking-formdata-in-unit-tests)
974 | - [ ] tests for GraphQL hooks `useMutation` + `useQuery`
975 | - [ ] tests for stale `response` see this [PR](https://github.com/ava/use-http/pull/119/files)
976 | - [ ] tests to make sure `response.formData()` and some of the other http `response methods` work properly
977 | - [ ] the `onMount` works properly with all variants of passing `useEffect(fn, [request.get])` and not causing an infinite loop
978 | - [ ] `async` tests for `interceptors.response`
979 | - [ ] aborts fetch on unmount
980 | - [ ] does not abort fetch on every rerender
981 | - [ ] `retryDelay` and `timeout` are both set. It works, but is annoying to deal with timers in tests. [resource](https://github.com/fac-13/HP-game/issues/9)
982 | - [ ] `timeout` with `retries > 0`. (also do `retires > 1`) Need to figure out how to advance timers properly to write this and the test above
983 | - [ ] take a look at how [react-apollo-hooks](https://github.com/trojanowski/react-apollo-hooks) work. Maybe ad `useSubscription` and `const request = useFetch(); request.subscribe()` or something along those lines
984 | - [ ] make this a github package
985 | - [see ava packages](https://github.com/orgs/ava/packages)
986 | - [ ] Documentation:
987 | - [ ] show comparison with Apollo
988 | - [ ] figure out a good way to show side-by-side comparisons
989 | - [ ] show comparison with Axios
990 | - [ ] potential option ideas
991 |
992 | ```jsx
993 | const request = useFetch({
994 | graphql: {
995 | // all options can also be put in here
996 | // to overwrite those of `useFetch` for
997 | // `useMutation` and `useQuery`
998 | },
999 | // by default this is true, but if set to false
1000 | // then we default to the responseType array of trying 'json' first, then 'text', etc.
1001 | // hopefully I get some answers on here: https://bit.ly/3afPlJS
1002 | responseTypeGuessing: true,
1003 |
1004 | // Allows you to pass in your own cache to useFetch
1005 | // This is controversial though because `cache` is an option in the requestInit
1006 | // and it's value is a string. See: https://developer.mozilla.org/en-US/docs/Web/API/Request/cache
1007 | // One possible solution is to move the default `fetch`'s `cache` to `cachePolicy`.
1008 | // I don't really like this solution though.
1009 | // Another solution is to only allow the `cache` option with the ``
1010 | cache: new Map(),
1011 | // these will be the exact same ones as Apollo's
1012 | cachePolicy: 'cache-and-network', 'network-only', 'cache-only', 'no-cache' // 'cache-first'
1013 | // potential idea to fetch on server instead of just having `loading` state. Not sure if this is a good idea though
1014 | onServer: true,
1015 | onSuccess: (/* idk what to put here */) => {},
1016 | // if you would prefer to pass the query in the config
1017 | query: `some graphql query`
1018 | // if you would prefer to pass the mutation in the config
1019 | mutation: `some graphql mutation`
1020 | refreshWhenHidden: false,
1021 | })
1022 |
1023 |
1024 | // potential for causing a rerender after clearing cache if needed
1025 | request.cache.clear(true)
1026 | ```
1027 |
1028 | - [ ] potential option ideas for `GraphQL`
1029 |
1030 | ```jsx
1031 | const request = useQuery({ onMount: true })`your graphql query`
1032 |
1033 | const request = useFetch(...)
1034 | const userID = 'some-user-uuid'
1035 | const res = await request.query({ userID })`
1036 | query Todos($userID string!) {
1037 | todos(userID: $userID) {
1038 | id
1039 | title
1040 | }
1041 | }
1042 | `
1043 | ```
1044 |
1045 | - [ ] make code editor plugin/package/extension that adds GraphQL syntax highlighting for `useQuery` and `useMutation` 😊
1046 |
1047 | - [ ] add React Native test suite
1048 |
1049 | [1]: https://github.com/ava/use-http/issues/new?title=[Feature%20Request]%20YOUR_FEATURE_NAME
1050 | [2]: https://github.com/ava/use-http/issues/93#issuecomment-600896722
1051 | [3]: https://github.com/ava/use-http/raw/master/public/dog.png
1052 | [4]: https://reactjs.org/docs/javascript-environment-requirements.html
1053 | [5]: https://use-http.com
1054 | [`react-app-polyfill`]: https://www.npmjs.com/package/react-app-polyfill
1055 |
--------------------------------------------------------------------------------
/config/jest.config.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line @typescript-eslint/no-var-requires
2 | const path = require('path')
3 |
4 | module.exports = {
5 | rootDir: process.cwd(),
6 | coverageDirectory: '/.coverage',
7 | globals: {
8 | __DEV__: true
9 | },
10 | collectCoverageFrom: [
11 | 'src/**/*.{js,jsx,ts,tsx}',
12 | '!src/**/*.d.ts',
13 | '!src/**/*.test.*',
14 | '!src/test/**/*.*'
15 | ],
16 | setupFilesAfterEnv: [path.join(__dirname, './setupTests.ts')],
17 | testMatch: [
18 | '/src/**/__tests__/**/*.ts?(x)',
19 | '/src/**/?(*.)(spec|test).ts?(x)'
20 | ],
21 | testEnvironment: 'node',
22 | testURL: 'http://localhost',
23 | transform: {
24 | '^.+\\.(ts|tsx)$': 'ts-jest'
25 | },
26 | transformIgnorePatterns: ['[/\\\\]node_modules[/\\\\].+\\.(js|jsx)$'],
27 | testPathIgnorePatterns: ['/src/__tests__/test-utils.tsx'],
28 | moduleNameMapper: {
29 | '^react-native$': 'react-native-web'
30 | },
31 | moduleFileExtensions: [
32 | 'web.js',
33 | 'js',
34 | 'json',
35 | 'web.jsx',
36 | 'jsx',
37 | 'ts',
38 | 'tsx',
39 | 'feature',
40 | 'csv'
41 | ]
42 | }
43 |
--------------------------------------------------------------------------------
/config/setupTests.ts:
--------------------------------------------------------------------------------
1 | import { GlobalWithFetchMock } from 'jest-fetch-mock'
2 |
3 | const customGlobal: GlobalWithFetchMock = global as GlobalWithFetchMock
4 | customGlobal.fetch = require('jest-fetch-mock')
5 | customGlobal.fetchMock = customGlobal.fetch
6 |
--------------------------------------------------------------------------------
/docs/.nojekyll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ava/use-http/6225d32f1ff40f46a98f65797015e8d86425de9e/docs/.nojekyll
--------------------------------------------------------------------------------
/docs/CNAME:
--------------------------------------------------------------------------------
1 | use-http.com
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | ![use-http logo][3]
2 |
3 |
43 | Need to fetch some data? Try this one out. It's an isomorphic fetch hook. That means it works with SSR (server side rendering).
44 |
45 |
46 |
47 | A note on the documentation below. Many of these examples could have performance improvements using useMemo and useCallback, but for the sake of the beginner/ease of reading, they are left out.
48 |
93 |
94 | Consider sponsoring
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 | Ava, Rapid Application Development
103 |
104 |
105 | Need a freelance software engineer with more than 5 years production experience at companies like Facebook, Discord, Best Buy, and Citrix?
106 | website | email | twitter
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 | Usage
115 | =============
116 |
117 | Basic Usage Auto-Managed State
118 | -------------------
119 |
120 | 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.
121 |
122 | ```js
123 | import useFetch from 'use-http'
124 |
125 | function Todos() {
126 | const options = {} // these options accept all native `fetch` options
127 | // the last argument below [] means it will fire onMount (GET by default)
128 | const { loading, error, data = [] } = useFetch('https://example.com/todos', options, [])
129 | return (
130 | <>
131 | {error && 'Error!'}
132 | {loading && 'Loading...'}
133 | {data.map(todo => (
134 |
{todo.title}
135 | )}
136 | >
137 | )
138 | }
139 | ```
140 |
141 |
142 |
143 |
144 | Managed State Usage
145 | -------------------
146 |
147 | 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.
148 |
149 | ```js
150 | import useFetch from 'use-http'
151 |
152 | function Todos() {
153 | const [todos, setTodos] = useState([])
154 | const { get, post, response, loading, error } = useFetch('https://example.com')
155 |
156 | useEffect(() => { loadInitialTodos() }, []) // componentDidMount
157 |
158 | async function loadInitialTodos() {
159 | const initialTodos = await get('/todos')
160 | if (response.ok) setTodos(initialTodos)
161 | }
162 |
163 | async function addTodo() {
164 | const newTodo = await post('/todos', { title: 'my new todo' })
165 | if (response.ok) setTodos([...todos, newTodo])
166 | }
167 |
168 | return (
169 | <>
170 |
171 | {error && 'Error!'}
172 | {loading && 'Loading...'}
173 | {todos.map(todo => (
174 |
{todo.title}
175 | )}
176 | >
177 | )
178 | }
179 | ```
180 |
181 |
182 |
183 | Suspense Mode Auto-Managed State
184 | ----------------------------------
185 |
186 | ```js
187 | import useFetch, { Provider } from 'use-http'
188 |
189 | function Todos() {
190 | const { data: todos = [] } = useFetch('/todos', {
191 | suspense: true // can put it in 2 places. Here or in Provider
192 | }, []) // onMount
193 |
194 | return todos.map(todo =>
)
238 | }
239 |
240 | function App() {
241 | const options = {
242 | suspense: true // B. can put `suspense: true` here too
243 | }
244 | return (
245 |
246 |
247 |
248 |
249 |
250 | )
251 | }
252 | ```
253 |
254 | Pagination With Provider
255 | ---------------------------
256 |
257 | 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.
258 |
259 | ```js
260 | import useFetch, { Provider } from 'use-http'
261 |
262 | const Todos = () => {
263 | const [page, setPage] = useState(1)
264 |
265 | const { data = [], loading } = useFetch(`/todos?page=${page}&amountPerPage=15`, {
266 | onNewData: (currTodos, newTodos) => [...currTodos, ...newTodos], // appends newly fetched todos
267 | perPage: 15, // stops making more requests if last todos fetched < 15
268 | }, [page]) // runs onMount AND whenever the `page` updates (onUpdate)
269 |
270 | return (
271 |
278 | )
279 | }
280 |
281 | const App = () => (
282 |
283 |
284 |
285 | )
286 | ```
287 |
288 |
289 |
290 | Destructured
291 | -------------
292 |
293 | ⚠️ Do not destructure the `response` object! Details in [this video](https://youtu.be/_-GujYZFCKI?list=PLZIwrWkE9rCdUybd8t3tY-mUMvXkCdenW&t=127). Technically you can do it, but if you need to access the `response.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`. ️️⚠️
294 |
295 | ```js
296 | var [request, response, loading, error] = useFetch('https://example.com')
297 |
298 | // want to use object destructuring? You can do that too
299 | var {
300 | request,
301 | response, // 🚨 Do not destructure the `response` object!
302 | loading,
303 | error,
304 | data,
305 | cache, // .has(), .clear(), .delete(), .get(), .set() (similar to JS Map)
306 | get,
307 | post,
308 | put,
309 | patch,
310 | delete // don't destructure `delete` though, it's a keyword
311 | del, // <- that's why we have this (del). or use `request.delete`
312 | head,
313 | options,
314 | connect,
315 | trace,
316 | mutate, // GraphQL
317 | query, // GraphQL
318 | abort
319 | } = useFetch('https://example.com')
320 |
321 | // 🚨 Do not destructure the `response` object!
322 | // 🚨 This just shows what fields are available in it.
323 | var {
324 | ok,
325 | status,
326 | headers,
327 | data,
328 | type,
329 | statusText,
330 | url,
331 | body,
332 | bodyUsed,
333 | redirected,
334 | // methods
335 | json,
336 | text,
337 | formData,
338 | blob,
339 | arrayBuffer,
340 | clone
341 | } = response
342 |
343 | var {
344 | loading,
345 | error,
346 | data,
347 | cache, // .has(), .clear(), .delete(), .get(), .set() (similar to JS Map)
348 | get,
349 | post,
350 | put,
351 | patch,
352 | delete // don't destructure `delete` though, it's a keyword
353 | del, // <- that's why we have this (del). or use `request.delete`
354 | mutate, // GraphQL
355 | query, // GraphQL
356 | abort
357 | } = request
358 | ```
359 |
360 |
361 |
362 | Relative routes
363 | ---------------
364 |
365 | ```js
366 | var request = useFetch('https://example.com')
367 |
368 | request.post('/todos', {
369 | no: 'way'
370 | })
371 | ```
372 |
373 |
374 |
375 | Abort
376 | -----
377 |
378 |
379 |
380 | ```js
381 | const { get, abort, loading, data: repos } = useFetch('https://api.github.com/search/repositories?q=')
382 |
383 | // the line below is not isomorphic, but for simplicity we're using the browsers `encodeURI`
384 | const searchGithubRepos = e => get(encodeURI(e.target.value))
385 |
386 | <>
387 |
388 |
389 | {loading ? 'Loading...' : repos?.data?.items?.map(repo => (
390 |
{repo.name}
391 | ))}
392 | >
393 | ```
394 |
395 |
396 |
397 | Request/Response Interceptors with `Provider`
398 | ---------------------------------------------
399 |
400 | This example shows how we can do authentication in the `request` interceptor and how we can camelCase the results in the `response` interceptor
401 |
402 | ```js
403 | import { Provider } from 'use-http'
404 | import { toCamel } from 'convert-keys'
405 |
406 | function App() {
407 | let [token, setToken] = useLocalStorage('token')
408 |
409 | const options = {
410 | interceptors: {
411 | // every time we make an http request, this will run 1st before the request is made
412 | // url, path and route are supplied to the interceptor
413 | // request options can be modified and must be returned
414 | request: async ({ options, url, path, route }) => {
415 | if (isExpired(token)) {
416 | token = await getNewToken()
417 | setToken(token)
418 | }
419 | options.headers.Authorization = `Bearer ${token}`
420 | return options
421 | },
422 | // every time we make an http request, before getting the response back, this will run
423 | response: async ({ response }) => {
424 | const res = response
425 | if (res.data) res.data = toCamel(res.data)
426 | return res
427 | }
428 | }
429 | }
430 |
431 | return (
432 |
433 |
434 |
435 | )
436 | }
437 |
438 | ```
439 |
440 |
441 |
442 | File Upload (FormData)
443 | ----------------------
444 |
445 | This example shows how we can upload a file using `useFetch`.
446 |
447 | ```js
448 | import useFetch from 'use-http'
449 |
450 | const FileUploader = () => {
451 | const [file, setFile] = useState()
452 |
453 | const { post } = useFetch('https://example.com/upload')
454 |
455 | const uploadFile = async () => {
456 | const data = new FormData()
457 | data.append('file', file)
458 | if (file instanceof FormData) await post(data)
459 | }
460 |
461 | return (
462 |
463 | {/* Drop a file onto the input below */}
464 | setFile(e.target.files[0])} />
465 |
466 |
467 | )
468 | }
469 | ```
470 |
471 | Handling Different Response Types
472 | ---------------------------------
473 |
474 | This example shows how we can get `.json()`, `.text()`, `.formData()`, `.blob()`, `.arrayBuffer()`, and all the other [http response methods](https://developer.mozilla.org/en-US/docs/Web/API/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.
475 |
476 | ```js
477 | import useFetch from 'use-http'
478 |
479 | const App = () => {
480 | const [name, setName] = useState('')
481 |
482 | const { get, loading, error, response } = useFetch('http://example.com')
483 |
484 | const handleClick = async () => {
485 | await get('/users/1?name=true') // will return just the user's name
486 | const text = await response.text()
487 | setName(text)
488 | }
489 |
490 | return (
491 | <>
492 |
493 | {error && error.messge}
494 | {loading && "Loading..."}
495 | {name &&
{name}
}
496 | >
497 | )
498 | }
499 | ```
500 |
501 |
502 |
503 |
504 | Overwrite/Remove Options/Headers Set in Provider
505 | ------------------------------------------------
506 |
507 | 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.
508 |
509 | ```js
510 | import useFetch from 'use-http'
511 |
512 | const Todos = () => {
513 | // let's say for this request, you don't want the `Accept` header at all
514 | const { loading, error, data: todos = [] } = useFetch('/todos', globalOptions => {
515 | delete globalOptions.headers.Accept
516 | return globalOptions
517 | }, []) // onMount
518 |
519 | return (
520 | <>
521 | {error && error.messge}
522 | {loading && "Loading..."}
523 | {todos &&
{todos.map(todo =>
{todo.title}
)}
}
524 | >
525 | )
526 | }
527 |
528 | const App = () => {
529 | const options = {
530 | headers: {
531 | Accept: 'application/json'
532 | }
533 | }
534 | return (
535 |
536 | }
537 | ```
538 |
539 |
540 |
541 | Retries
542 | -------
543 |
544 | 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`, not on any generic network error.
545 |
546 | ```js
547 | import useFetch from 'use-http'
548 |
549 | const TestRetry = () => {
550 | const { response, get } = useFetch('https://httpbin.org/status/305', {
551 | // make sure `retries` is set otherwise it won't retry
552 | retries: 1,
553 | retryOn: [305],
554 | // OR
555 | retryOn: async ({ attempt, error, response }) => {
556 | // returns true or false to determine whether to retry
557 | return error || response && response.status >= 300
558 | },
559 |
560 | retryDelay: 3000,
561 | // OR
562 | retryDelay: ({ attempt, error, response }) => {
563 | // exponential backoff
564 | return Math.min(attempt > 1 ? 2 ** attempt * 1000 : 1000, 30 * 1000)
565 | // linear backoff
566 | return attempt * 1000
567 | }
568 | })
569 |
570 | return (
571 | <>
572 |
573 |
}
696 | >
697 | )
698 | }
699 | ```
700 |
701 | Adding the Provider
702 | -------------------
703 |
704 | These props are defaults used in every request inside the ``. They can be overwritten individually
705 |
706 | ```js
707 | function App() {
708 |
709 | const options = {
710 | headers: {
711 | Authorization: 'Bearer jwt-asdfasdfasdf'
712 | }
713 | }
714 |
715 | return (
716 |
717 |
718 |
719 |
720 | )
721 | }
722 | ```
723 |
724 | Hooks
725 | =======
726 |
727 | | Option | Description |
728 | | --------------------- | ------------------ |
729 | | `useFetch` | The base hook |
730 | | `useQuery` | For making a GraphQL query |
731 | | `useMutation` | For making a GraphQL mutation |
732 |
733 | Options
734 | ========
735 |
736 | 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 ``
737 |
738 | | Option | Description | Default |
739 | | --------------------- | --------------------------------------------------------------------------|------------- |
740 | | `cacheLife` | After a successful cache update, that cache data will become stale after this duration | `0` |
741 | | `cachePolicy` | These will be the same ones as Apollo's [fetch policies](https://www.apollographql.com/docs/react/api/react/hoc/#optionsfetchpolicy). Possible values are `cache-and-network`, `network-only`, `cache-only`, `no-cache`, `cache-first`. Currently only supports **`cache-first`** or **`no-cache`** | `cache-first` |
742 | | `data` | Allows you to set a default value for `data` | `undefined` |
743 | | `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` |
744 | | `interceptors.response` | Allows you to do something after an http response is recieved. Useful for something like camelCasing the keys of the response. | `undefined` |
745 | | `loading` | Allows you to set default value for `loading` | `false` unless the last argument of `useFetch` is `[]` |
746 | | `onAbort` | Runs when the request is aborted. | empty function |
747 | | `onError` | Runs when the request get's an error. If retrying, it is only called on the last retry attempt. | empty function |
748 | | `onNewData` | Merges the current data with the incoming data. Great for pagination. | `(curr, new) => new` |
749 | | `onTimeout` | Called when the request times out. | empty function |
750 | | `persist` | Persists data for the duration of `cacheLife`. If `cacheLife` is not set it defaults to 24h. Currently only available in Browser. | `false` |
751 | | `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](https://developer.mozilla.org/en-US/docs/Web/API/Body/formData). | `['json', 'text', 'blob', 'readableStream']` |
752 | | `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` |
753 | | `retries` | When a request fails or times out, retry the request this many times. By default it will not retry. | `0` |
754 | | `retryDelay` | You can retry with certain intervals i.e. 30 seconds `30000` or with custom logic (i.e. to increase retry intervals). | `1000` |
755 | | `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. | `[]` |
756 | | `suspense` | Enables Experimental React Suspense mode. [example](https://codesandbox.io/s/usefetch-suspense-i22wv) | `false` |
757 | | `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` |
758 |
759 | ```jsx
760 | const options = {
761 | // accepts all `fetch` options such as headers, method, etc.
762 |
763 | // The time in milliseconds that cache data remains fresh.
764 | cacheLife: 0,
765 |
766 | // Cache responses to improve speed and reduce amount of requests
767 | // Only one request to the same endpoint will be initiated unless cacheLife expires for 'cache-first'.
768 | cachePolicy: 'cache-first' // 'no-cache'
769 |
770 | // set's the default for the `data` field
771 | data: [],
772 |
773 | // typically, `interceptors` would be added as an option to the ``
774 | interceptors: {
775 | request: async ({ options, url, path, route }) => { // `async` is not required
776 | return options // returning the `options` is important
777 | },
778 | response: async ({ response }) => {
779 | // note: `response.data` is equivalent to `await response.json()`
780 | return response // returning the `response` is important
781 | }
782 | },
783 |
784 | // set's the default for `loading` field
785 | loading: false,
786 |
787 | // called when aborting the request
788 | onAbort: () => {},
789 |
790 | // runs when an error happens.
791 | onError: ({ error }) => {},
792 |
793 | // this will allow you to merge the `data` for pagination.
794 | onNewData: (currData, newData) => {
795 | return [...currData, ...newData]
796 | },
797 |
798 | // called when the request times out
799 | onTimeout: () => {},
800 |
801 | // this will tell useFetch not to run the request if the list doesn't haveMore. (pagination)
802 | // i.e. if the last page fetched was < 15, don't run the request again
803 | perPage: 15,
804 |
805 | // Allows caching to persist after page refresh. Only supported in the Browser currently.
806 | persist: false,
807 |
808 | // this would basically call `await response.json()`
809 | // and set the `data` and `response.data` field to the output
810 | responseType: 'json',
811 | // OR can be an array. It's an array by default.
812 | // We will try to get the `data` by attempting to extract
813 | // it via these body interface methods, one by one in
814 | // this order. We skip `formData` because it's mostly used
815 | // for service workers.
816 | responseType: ['json', 'text', 'blob', 'arrayBuffer'],
817 |
818 | // amount of times it should retry before erroring out
819 | retries: 3,
820 |
821 | // The time between retries
822 | retryDelay: 10000,
823 | // OR
824 | // Can be a function which is used if we want change the time in between each retry
825 | retryDelay({ attempt, error, response }) {
826 | // exponential backoff
827 | return Math.min(attempt > 1 ? 2 ** attempt * 1000 : 1000, 30 * 1000)
828 | // linear backoff
829 | return attempt * 1000
830 | },
831 |
832 |
833 | // make sure `retries` is set otherwise it won't retry
834 | // can retry on certain http status codes
835 | retryOn: [503],
836 | // OR
837 | async retryOn({ attempt, error, response }) {
838 | // retry on any network error, or 4xx or 5xx status codes
839 | if (error !== null || response.status >= 400) {
840 | console.log(`retrying, attempt number ${attempt + 1}`);
841 | return true;
842 | }
843 | },
844 |
845 | // enables experimental React Suspense mode
846 | suspense: true, // defaults to `false`
847 |
848 | // amount of time before the request get's canceled/aborted
849 | timeout: 10000,
850 | }
851 |
852 | useFetch(options)
853 | // OR
854 |
855 | ```
856 |
857 | Who's using use-http?
858 | =====================
859 |
860 |