Skip to content

Commit 436ca3d

Browse files
author
Alex Cory
committed
Merge branch 'master' of github.com:alex-cory/react-usefetch
2 parents 0ad6650 + f6d6bc3 commit 436ca3d

File tree

7 files changed

+76
-17
lines changed

7 files changed

+76
-17
lines changed

README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -850,6 +850,7 @@ This is exactly what you would pass to the normal js `fetch`, with a little extr
850850
| `interceptors.response` | Allows you to do something after an http response is recieved. Useful for something like camelCasing the keys of the response. | `undefined` |
851851
| `loading` | Allows you to set default value for `loading` | `false` unless the last argument of `useFetch` is `[]` |
852852
| `onAbort` | Runs when the request is aborted. | empty function |
853+
| `onError` | Runs when the request get's an error. If retrying, it is only called on the last retry attempt. | empty function |
853854
| `onNewData` | Merges the current data with the incoming data. Great for pagination. | `(curr, new) => new` |
854855
| `onTimeout` | Called when the request times out. | empty function |
855856
| `path` | When using a global `url` set in the `Provider`, this is useful for adding onto it | `''` |
@@ -894,6 +895,9 @@ const options = {
894895
// called when aborting the request
895896
onAbort: () => {},
896897

898+
// runs when an error happens.
899+
onError: ({ error }) => {},
900+
897901
// this will allow you to merge the `data` for pagination.
898902
onNewData: (currData, newData) => {
899903
return [...currData, ...newData]
@@ -1062,12 +1066,10 @@ Todos
10621066
// potential idea to fetch on server instead of just having `loading` state. Not sure if this is a good idea though
10631067
onServer: true,
10641068
onSuccess: (/* idk what to put here */) => {},
1065-
onError: (error) => {},
10661069
// if you would prefer to pass the query in the config
10671070
query: `some graphql query`
10681071
// if you would prefer to pass the mutation in the config
10691072
mutation: `some graphql mutation`
1070-
retryOnError: false,
10711073
refreshWhenHidden: false,
10721074
})
10731075

docs/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -801,6 +801,7 @@ This is exactly what you would pass to the normal js `fetch`, with a little extr
801801
| `interceptors.response` | Allows you to do something after an http response is recieved. Useful for something like camelCasing the keys of the response. | `undefined` |
802802
| `loading` | Allows you to set default value for `loading` | `false` unless the last argument of `useFetch` is `[]` |
803803
| `onAbort` | Runs when the request is aborted. | empty function |
804+
| `onError` | Runs when the request get's an error. If retrying, it is only called on the last retry attempt. | empty function |
804805
| `onNewData` | Merges the current data with the incoming data. Great for pagination. | `(curr, new) => new` |
805806
| `onTimeout` | Called when the request times out. | empty function |
806807
| `path` | When using a global `url` set in the `Provider`, this is useful for adding onto it | `''` |
@@ -845,6 +846,9 @@ const options = {
845846
// called when aborting the request
846847
onAbort: () => {},
847848

849+
// runs when an error happens.
850+
onError: ({ error }) => {},
851+
848852
// this will allow you to merge the `data` for pagination.
849853
onNewData: (currData, newData) => {
850854
return [...currData, ...newData]

src/__tests__/useFetch.test.tsx

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1010,11 +1010,55 @@ describe('useFetch - BROWSER - errors', (): void => {
10101010
fetch.mockResponseOnce(JSON.stringify(expectedSuccess))
10111011
})
10121012

1013+
it('should call onError when there is a network error', async (): Promise<void> => {
1014+
const onError = jest.fn()
1015+
const { waitForNextUpdate } = renderHook(
1016+
() => useFetch('https://example.com', { onError }, [])
1017+
)
1018+
await waitForNextUpdate()
1019+
expect(onError).toBeCalled()
1020+
expect(onError).toHaveBeenCalledWith({ error: expectedError })
1021+
})
1022+
1023+
it('should not call onError when aborting a request', async (): Promise<void> => {
1024+
fetch.resetMocks()
1025+
fetch.mockResponse('fail', { status: 401 })
1026+
const onError = jest.fn()
1027+
const { result, waitForNextUpdate } = renderHook(
1028+
() => useFetch('https://example.com', { onError }, [])
1029+
)
1030+
act(result.current.abort)
1031+
await waitForNextUpdate()
1032+
expect(onError).not.toBeCalled()
1033+
})
1034+
1035+
it('should only call onError on the last retry', async (): Promise<void> => {
1036+
fetch.resetMocks()
1037+
fetch.mockResponse('fail', { status: 401 })
1038+
const onError = jest.fn()
1039+
const { waitForNextUpdate } = renderHook(
1040+
() => useFetch('https://example.com/4', { onError, retries: 1 }, [])
1041+
)
1042+
await waitForNextUpdate()
1043+
expect(onError).toBeCalledTimes(1)
1044+
expect(onError).toHaveBeenCalledWith({ error: makeError(401, 'Unauthorized') })
1045+
})
1046+
1047+
it('should call onError when !response.ok', async (): Promise<void> => {
1048+
fetch.resetMocks()
1049+
fetch.mockResponse('fail', { status: 401 })
1050+
const onError = jest.fn()
1051+
const { waitForNextUpdate } = renderHook(
1052+
() => useFetch('https://example.com', { onError }, [])
1053+
)
1054+
await waitForNextUpdate()
1055+
expect(onError).toBeCalled()
1056+
expect(onError).toHaveBeenCalledWith({ error: makeError(401, 'Unauthorized') })
1057+
})
1058+
10131059
it('should set the `error` object when response.ok is false', async (): Promise<void> => {
10141060
fetch.resetMocks()
1015-
fetch.mockResponse('fail', {
1016-
status: 401
1017-
})
1061+
fetch.mockResponse('fail', { status: 401 })
10181062
const { result } = renderHook(
10191063
() => useFetch({
10201064
url: 'https://example.com',

src/defaults.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export const useFetchArgsDefaults: UseFetchArgsReturn = {
88
cachePolicy: CachePolicies.CACHE_FIRST,
99
interceptors: {},
1010
onAbort: () => { /* do nothing */ },
11+
onError: () => { /* do nothing */ },
1112
onNewData: (currData: any, newData: any) => newData,
1213
onTimeout: () => { /* do nothing */ },
1314
path: '',

src/types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@ export interface CustomOptions {
180180
interceptors?: Interceptors
181181
loading?: boolean
182182
onAbort?: () => void
183+
onError?: OnError
183184
onNewData?: (currData: any, newData: any) => any
184185
onTimeout?: () => void
185186
path?: string
@@ -211,12 +212,15 @@ export type RetryDelay = (<TData = any>({ attempt, error, response }: RetryOpts)
211212
export type BodyInterfaceMethods = Exclude<FunctionKeys<Body>, 'body' | 'bodyUsed' | 'formData'>
212213
export type ResponseType = BodyInterfaceMethods | BodyInterfaceMethods[]
213214

215+
export type OnError = ({ error }: { error: Error }) => void
216+
214217
export type UseFetchArgsReturn = {
215218
customOptions: {
216219
cacheLife: number
217220
cachePolicy: CachePolicies
218221
interceptors: Interceptors
219222
onAbort: () => void
223+
onError: OnError
220224
onNewData: (currData: any, newData: any) => any
221225
onTimeout: () => void
222226
path: string

src/useFetch.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ function useFetch<TData = any>(...args: UseFetchArgs): UseFetch<TData> {
3232
cachePolicy, // 'cache-first' by default
3333
interceptors,
3434
onAbort,
35+
onError,
3536
onNewData,
3637
onTimeout,
3738
path,
@@ -174,6 +175,7 @@ function useFetch<TData = any>(...args: UseFetchArgs): UseFetch<TData> {
174175
if (newRes && !newRes.ok && !error.current) error.current = makeError(newRes.status, newRes.statusText)
175176
if (!suspense) setLoading(false)
176177
if (attempt.current === retries) attempt.current = 0
178+
if (error.current) onError({ error: error.current })
177179

178180
return data.current
179181
} // end of doFetch()

src/useFetchArgs.ts

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { OptionsMaybeURL, NoUrlOptions, CachePolicies, Interceptors, OverwriteGlobalOptions, Options, RetryOn, RetryDelay, UseFetchArgsReturn, ResponseType } from './types'
1+
import { OptionsMaybeURL, NoUrlOptions, CachePolicies, Interceptors, OverwriteGlobalOptions, Options, RetryOn, RetryDelay, UseFetchArgsReturn, ResponseType, OnError } from './types'
22
import { isString, isObject, invariant, pullOutRequestInit, isFunction, isPositiveNumber } from './utils'
33
import { useContext, useMemo } from 'react'
44
import FetchContext from './FetchContext'
@@ -62,25 +62,26 @@ export default function useFetchArgs(
6262
}, [optionsNoURLsOrOverwriteGlobalOrDeps, deps])
6363

6464
const data = useField('data', urlOrOptions, optionsNoURLs)
65-
const path = useField<string>('path', urlOrOptions, optionsNoURLs)
66-
const timeout = useField<number>('timeout', urlOrOptions, optionsNoURLs)
67-
const persist = useField<boolean>('persist', urlOrOptions, optionsNoURLs)
65+
const cacheLife = useField<number>('cacheLife', urlOrOptions, optionsNoURLs)
66+
invariant(Number.isInteger(cacheLife) && cacheLife >= 0, '`cacheLife` must be a number >= 0')
67+
const cachePolicy = useField<CachePolicies>('cachePolicy', urlOrOptions, optionsNoURLs)
6868
const onAbort = useField<() => void>('onAbort', urlOrOptions, optionsNoURLs)
69-
const onTimeout = useField<() => void>('onTimeout', urlOrOptions, optionsNoURLs)
69+
const onError = useField<OnError>('onError', urlOrOptions, optionsNoURLs)
7070
const onNewData = useField<() => void>('onNewData', urlOrOptions, optionsNoURLs)
71+
const onTimeout = useField<() => void>('onTimeout', urlOrOptions, optionsNoURLs)
72+
const path = useField<string>('path', urlOrOptions, optionsNoURLs)
7173
const perPage = useField<number>('perPage', urlOrOptions, optionsNoURLs)
72-
const cachePolicy = useField<CachePolicies>('cachePolicy', urlOrOptions, optionsNoURLs)
73-
const cacheLife = useField<number>('cacheLife', urlOrOptions, optionsNoURLs)
74-
invariant(Number.isInteger(cacheLife) && cacheLife >= 0, '`cacheLife` must be a number >= 0')
75-
const suspense = useField<boolean>('suspense', urlOrOptions, optionsNoURLs)
74+
const persist = useField<boolean>('persist', urlOrOptions, optionsNoURLs)
75+
const responseType = useField<ResponseType>('responseType', urlOrOptions, optionsNoURLs)
7676
const retries = useField<number>('retries', urlOrOptions, optionsNoURLs)
7777
invariant(Number.isInteger(retries) && retries >= 0, '`retries` must be a number >= 0')
78+
const retryDelay = useField<RetryDelay>('retryDelay', urlOrOptions, optionsNoURLs)
79+
invariant(isFunction(retryDelay) || Number.isInteger(retryDelay as number) && retryDelay >= 0, '`retryDelay` must be a positive number or a function returning a positive number.')
7880
const retryOn = useField<RetryOn>('retryOn', urlOrOptions, optionsNoURLs)
7981
const isValidRetryOn = isFunction(retryOn) || (Array.isArray(retryOn) && retryOn.every(isPositiveNumber))
8082
invariant(isValidRetryOn, '`retryOn` must be an array of positive numbers or a function returning a boolean.')
81-
const retryDelay = useField<RetryDelay>('retryDelay', urlOrOptions, optionsNoURLs)
82-
invariant(isFunction(retryDelay) || Number.isInteger(retryDelay as number) && retryDelay >= 0, '`retryDelay` must be a positive number or a function returning a positive number.')
83-
const responseType = useField<ResponseType>('responseType', urlOrOptions, optionsNoURLs)
83+
const suspense = useField<boolean>('suspense', urlOrOptions, optionsNoURLs)
84+
const timeout = useField<number>('timeout', urlOrOptions, optionsNoURLs)
8485

8586
const loading = useMemo((): boolean => {
8687
if (isObject(urlOrOptions)) return !!urlOrOptions.loading || Array.isArray(dependencies)
@@ -130,6 +131,7 @@ export default function useFetchArgs(
130131
cachePolicy,
131132
interceptors,
132133
onAbort,
134+
onError,
133135
onNewData,
134136
onTimeout,
135137
path,

0 commit comments

Comments
 (0)