Skip to content

Commit

Permalink
feat(useFetch): introduce updateDataOnError option (#3092)
Browse files Browse the repository at this point in the history
Co-authored-by: Anthony Fu <anthonyfu117@hotmail.com>
  • Loading branch information
climba03003 and antfu committed Aug 25, 2023
1 parent bc9665d commit 945ca16
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 9 deletions.
7 changes: 5 additions & 2 deletions packages/core/useFetch/index.md
Expand Up @@ -113,19 +113,22 @@ const { data } = useFetch(url, {
})
```

The `onFetchError` option can intercept the response data and error before it is updated.
The `onFetchError` option can intercept the response data and error before it is updated when `updateDataOnError` is set to `true`.

```ts
const { data } = useFetch(url, {
updateDataOnError: true,
onFetchError(ctx) {
// ctx.data can be null when 5xx response
if (ctx.data === null)
ctx.data = { title: 'Hunter x Hunter' } // Modifies the response data

ctx.error = new Error('Custom Error') // Modifies the error

return ctx
},
})

console.log(data.value) // { title: 'Hunter x Hunter' }
```

### Setting the request method and return type
Expand Down
18 changes: 18 additions & 0 deletions packages/core/useFetch/index.test.ts
Expand Up @@ -542,6 +542,7 @@ describe.skipIf(isBelowNode18)('useFetch', () => {
const { data, error, statusCode } = useFetch('https://example.com?status=400&json', {
onFetchError(ctx) {
ctx.error = 'Internal Server Error'
ctx.data = 'Internal Server Error'
return ctx
},
}).json()
Expand All @@ -553,6 +554,23 @@ describe.skipIf(isBelowNode18)('useFetch', () => {
})
})

it('should return data in onFetchError when updateDataOnError is true', async () => {
const { data, error, statusCode } = useFetch('https://example.com?status=400&json', {
updateDataOnError: true,
onFetchError(ctx) {
ctx.error = 'Internal Server Error'
ctx.data = 'Internal Server Error'
return ctx
},
}).json()

await retry(() => {
expect(statusCode.value).toEqual(400)
expect(error.value).toEqual('Internal Server Error')
expect(data.value).toEqual('Internal Server Error')
})
})

it('should run the onFetchError function when network error', async () => {
const { data, error, statusCode } = useFetch('https://example.com?status=500&text=Internal%20Server%20Error', {
onFetchError(ctx) {
Expand Down
45 changes: 38 additions & 7 deletions packages/core/useFetch/index.ts
Expand Up @@ -163,6 +163,13 @@ export interface UseFetchOptions {
*/
timeout?: number

/**
* Allow update the `data` ref when fetch error whenever provided, or mutated in the `onFetchError` callback
*
* @default false
*/
updateDataOnError?: boolean

/**
* Will run immediately before the fetch request is dispatched
*/
Expand Down Expand Up @@ -211,7 +218,7 @@ export interface CreateFetchOptions {
* to include the new options
*/
function isFetchOptions(obj: object): obj is UseFetchOptions {
return obj && containsProp(obj, 'immediate', 'refetch', 'initialData', 'timeout', 'beforeFetch', 'afterFetch', 'onFetchError', 'fetch')
return obj && containsProp(obj, 'immediate', 'refetch', 'initialData', 'timeout', 'beforeFetch', 'afterFetch', 'onFetchError', 'fetch', 'updateDataOnError')
}

// A URL is considered absolute if it begins with "<scheme>://" or "//" (protocol-relative URL).
Expand Down Expand Up @@ -314,8 +321,20 @@ export function useFetch<T>(url: MaybeRefOrGetter<string>, ...args: any[]): UseF
const supportsAbort = typeof AbortController === 'function'

let fetchOptions: RequestInit = {}
let options: UseFetchOptions = { immediate: true, refetch: false, timeout: 0 }
interface InternalConfig { method: HttpMethod; type: DataType; payload: unknown; payloadType?: string }
let options: UseFetchOptions = {
immediate: true,
refetch: false,
timeout: 0,
updateDataOnError: false,
}

interface InternalConfig {
method: HttpMethod
type: DataType
payload: unknown
payloadType?: string
}

const config: InternalConfig = {
method: 'GET',
type: 'text' as DataType,
Expand Down Expand Up @@ -454,8 +473,12 @@ export function useFetch<T>(url: MaybeRefOrGetter<string>, ...args: any[]): UseF
throw new Error(fetchResponse.statusText)
}

if (options.afterFetch)
({ data: responseData } = await options.afterFetch({ data: responseData, response: fetchResponse }))
if (options.afterFetch) {
({ data: responseData } = await options.afterFetch({
data: responseData,
response: fetchResponse,
}))
}
data.value = responseData

responseEvent.trigger(fetchResponse)
Expand All @@ -464,9 +487,17 @@ export function useFetch<T>(url: MaybeRefOrGetter<string>, ...args: any[]): UseF
.catch(async (fetchError) => {
let errorData = fetchError.message || fetchError.name

if (options.onFetchError)
({ error: errorData } = await options.onFetchError({ data: responseData, error: fetchError, response: response.value }))
if (options.onFetchError) {
({ error: errorData, data: responseData } = await options.onFetchError({
data: responseData,
error: fetchError,
response: response.value,
}))
}

error.value = errorData
if (options.updateDataOnError)
data.value = responseData

errorEvent.trigger(fetchError)
if (throwOnFailed)
Expand Down

0 comments on commit 945ca16

Please sign in to comment.