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

fix(useFetch): Add option to use stock FetchAPI error-like status codes behavior (non-breaking alternative) #3126

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions packages/core/useFetch/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,21 @@ const { data } = useFetch(url, {
})
```

### Swallowing errors

You can disable the `throwOnErrCodes` option to handle error-codes like the native FetchAPI does, which is to just accept them.

By default, `useFetch` skips parsing the body and throws early to provide the `error`
object when it encounters any response code that is not `200: Ok`; disabling `throwOnErrCodes` continues parsing
like a normal success when it encounters error codes.

You can implement your own error-detection yourself by throwing inside the `afterFetch()` function.

```ts
const { data } = useFetch('bad-url/400', { throwOnErrCodes: false })
console.log(data.value) // null
```

### Setting the request method and return type

The request method and return type can be set by adding the appropriate methods to the end of `useFetch`
Expand Down
25 changes: 25 additions & 0 deletions packages/core/useFetch/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,15 @@ describe('useFetch', () => {
})
})

it('should not have an error on when not throwOnErrCodes', async () => {
const { error, statusCode, data } = useFetch('https://example.com?status=400', { throwOnErrCodes: false })
await retry(() => {
expect(data.value).toBe('')
expect(statusCode.value).toBe(400)
expect(error.value).toBe(null)
})
})

it('should throw error', async () => {
const options = { immediate: false }
const error1 = await useFetch('https://example.com?status=400', options).execute(true).catch(err => err)
Expand Down Expand Up @@ -569,6 +578,22 @@ describe('useFetch', () => {
})
})

it('should act like error code on custom throw inside afterFetch', async () => {
const { error, statusCode, data } = useFetch('https://example.com?status=400',
{
throwOnErrCodes: false,
afterFetch(ctx) {
throw new Error('custom fetch error')
return ctx
},
})
await retry(() => {
expect(data.value).toBeNull()
expect(statusCode.value).toStrictEqual(400)
expect(error.value).toBe('custom fetch error')
})
})

it('should emit onFetchResponse event', async () => {
const onResponseSpy = vi.fn()
const { onFetchResponse } = useFetch('https://example.com')
Expand Down
14 changes: 11 additions & 3 deletions packages/core/useFetch/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,14 @@ export interface UseFetchOptions {
*/
timeout?: number

/**
* Throw if the http status code is not a 2xx code.
* (this deviates from stock FetchAPI behavior)
*
* @default true
*/
throwOnErrCodes?: boolean

/**
* Will run immediately before the fetch request is dispatched
*/
Expand Down Expand Up @@ -211,7 +219,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', 'throwOnErrCodes', 'beforeFetch', 'afterFetch', 'onFetchError', 'fetch')
}

// A URL is considered absolute if it begins with "<scheme>://" or "//" (protocol-relative URL).
Expand Down Expand Up @@ -314,7 +322,7 @@ 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 }
let options: UseFetchOptions = { immediate: true, refetch: false, timeout: 0, throwOnErrCodes: true }
interface InternalConfig { method: HttpMethod; type: DataType; payload: unknown; payloadType?: string }
const config: InternalConfig = {
method: 'GET',
Expand Down Expand Up @@ -444,7 +452,7 @@ export function useFetch<T>(url: MaybeRefOrGetter<string>, ...args: any[]): UseF
responseData = await fetchResponse[config.type]()

// see: https://www.tjvantoll.com/2015/09/13/fetch-and-errors/
if (!fetchResponse.ok) {
if (options.throwOnErrCodes && !fetchResponse.ok) {
data.value = initialData || null
throw new Error(fetchResponse.statusText)
}
Expand Down