Skip to content

Commit ca5081d

Browse files
fix(vue-query): useErrorBoundary support in useQuery and useMutation (#5826)
--------- Co-authored-by: Anton Kolesov <[email protected]> Co-authored-by: Damian Osipiuk <[email protected]>
1 parent fd804b2 commit ca5081d

File tree

5 files changed

+85
-2
lines changed

5 files changed

+85
-2
lines changed

packages/vue-query/src/__tests__/useMutation.test.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,4 +332,24 @@ describe('useMutation', () => {
332332
})
333333
})
334334
})
335+
336+
describe('throwOnError', () => {
337+
test('should evaluate throwOnError when mutation is expected to throw', async () => {
338+
const err = new Error('Expected mock error. All is well!')
339+
const boundaryFn = vi.fn()
340+
const { mutate } = useMutation({
341+
mutationFn: () => {
342+
return Promise.reject(err)
343+
},
344+
throwOnError: boundaryFn,
345+
})
346+
347+
mutate()
348+
349+
await flushPromises()
350+
351+
expect(boundaryFn).toHaveBeenCalledTimes(1)
352+
expect(boundaryFn).toHaveBeenCalledWith(err)
353+
})
354+
})
335355
})

packages/vue-query/src/__tests__/useQuery.test.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,28 @@ describe('useQuery', () => {
235235
expect(status.value).toStrictEqual('pending')
236236
})
237237

238+
describe('throwOnError', () => {
239+
test('should evaluate throwOnError when query is expected to throw', async () => {
240+
const boundaryFn = vi.fn()
241+
useQuery({
242+
queryKey: ['key0'],
243+
queryFn: rejectFetcher,
244+
retry: false,
245+
throwOnError: boundaryFn,
246+
})
247+
248+
await flushPromises()
249+
250+
expect(boundaryFn).toHaveBeenCalledTimes(1)
251+
expect(boundaryFn).toHaveBeenCalledWith(
252+
Error('Some error'),
253+
expect.objectContaining({
254+
state: expect.objectContaining({ status: 'error' }),
255+
}),
256+
)
257+
})
258+
})
259+
238260
describe('suspense', () => {
239261
test('should return a Promise', () => {
240262
const getCurrentInstanceSpy = getCurrentInstance as Mock

packages/vue-query/src/useBaseQuery.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
watch,
88
} from 'vue-demi'
99
import { useQueryClient } from './useQueryClient'
10-
import { cloneDeepUnref, updateState } from './utils'
10+
import { cloneDeepUnref, shouldThrowError, updateState } from './utils'
1111
import type { ToRefs } from 'vue-demi'
1212
import type {
1313
DefaultedQueryObserverOptions,
@@ -146,6 +146,23 @@ export function useBaseQuery<
146146
)
147147
}
148148

149+
// Handle error boundary
150+
watch(
151+
() => state.error,
152+
(error) => {
153+
if (
154+
state.isError &&
155+
!state.isFetching &&
156+
shouldThrowError(defaultedOptions.value.throwOnError, [
157+
error as TError,
158+
observer.getCurrentQuery(),
159+
])
160+
) {
161+
throw error
162+
}
163+
},
164+
)
165+
149166
return {
150167
...(toRefs(readonly(state)) as ToRefs<
151168
Readonly<QueryObserverResult<TData, TError>>

packages/vue-query/src/useMutation.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
watch,
88
} from 'vue-demi'
99
import { MutationObserver } from '@tanstack/query-core'
10-
import { cloneDeepUnref, updateState } from './utils'
10+
import { cloneDeepUnref, shouldThrowError, updateState } from './utils'
1111
import { useQueryClient } from './useQueryClient'
1212
import type { ToRefs } from 'vue-demi'
1313
import type {
@@ -100,6 +100,18 @@ export function useMutation<
100100
Readonly<MutationResult<TData, TError, TVariables, TContext>>
101101
>
102102

103+
watch(
104+
() => state.error,
105+
(error) => {
106+
if (
107+
error &&
108+
shouldThrowError(options.value.throwOnError, [error as TError])
109+
) {
110+
throw error
111+
}
112+
},
113+
)
114+
103115
return {
104116
...resultRefs,
105117
mutate,

packages/vue-query/src/utils.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,15 @@ function isPlainObject(value: unknown): value is Object {
6565
const prototype = Object.getPrototypeOf(value)
6666
return prototype === null || prototype === Object.prototype
6767
}
68+
69+
export function shouldThrowError<T extends (...args: any[]) => boolean>(
70+
throwOnError: boolean | T | undefined,
71+
params: Parameters<T>,
72+
): boolean {
73+
// Allow throwOnError function to override throwing behavior on a per-error basis
74+
if (typeof throwOnError === 'function') {
75+
return throwOnError(...params)
76+
}
77+
78+
return !!throwOnError
79+
}

0 commit comments

Comments
 (0)