Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(useFetch): introduce updateDataOnError option #3092

Merged
merged 11 commits into from Aug 25, 2023
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