Skip to content

Commit 0eeb023

Browse files
louiscklawphatmann
authored andcommitted
Bail out if query data undefined
1 parent 2b5c337 commit 0eeb023

File tree

11 files changed

+171
-77
lines changed

11 files changed

+171
-77
lines changed

docs/src/pages/reference/QueryClient.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,8 @@ queryClient.setQueryData(queryKey, updater)
224224
setQueryData(queryKey, newData)
225225
```
226226

227+
If the value is `undefined`, the query data is not updated.
228+
227229
**Using an updater function**
228230

229231
For convenience in syntax, you can also pass an updater function which receives the current data value and returns the new one:
@@ -232,6 +234,8 @@ For convenience in syntax, you can also pass an updater function which receives
232234
setQueryData(queryKey, oldData => newData)
233235
```
234236

237+
If the updater function returns `undefined`, the query data will not be updated.
238+
235239
## `queryClient.getQueryState`
236240

237241
`getQueryState` is a synchronous function that can be used to get an existing query's state. If the query does not exist, `undefined` will be returned.

docs/src/pages/reference/useQuery.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ const result = useQuery({
6868
6969
**Options**
7070
71-
- `queryKey: unknown[]`
71+
- `queryKey: unknown[]`
7272
- **Required**
7373
- The query key to use for this query.
7474
- The query key will be hashed into a stable hash. See [Query Keys](../guides/query-keys) for more information.
@@ -77,7 +77,7 @@ const result = useQuery({
7777
- **Required, but only if no default query function has been defined** See [Default Query Function](../guides/default-query-function) for more information.
7878
- The function that the query will use to request data.
7979
- Receives a [QueryFunctionContext](../guides/query-functions#queryfunctioncontext)
80-
- Must return a promise that will either resolve data or throw an error.
80+
- Must return a promise that will either resolve data or throw an error. The data cannot be `undefined`.
8181
- `enabled: boolean`
8282
- Set this to `false` to disable this query from automatically running.
8383
- Can be used for [Dependent Queries](../guides/dependent-queries).

src/core/query.ts

Lines changed: 32 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
import {
22
getAbortController,
3-
Updater,
4-
functionalUpdate,
53
noop,
64
replaceEqualDeep,
75
timeUntilStale,
@@ -195,14 +193,11 @@ export class Query<
195193
}
196194

197195
setData(
198-
updater: Updater<TData | undefined, TData>,
196+
data: TData,
199197
options?: SetDataOptions & { notifySuccess: boolean }
200198
): TData {
201199
const prevData = this.state.data
202200

203-
// Get the new data
204-
let data = functionalUpdate(updater, prevData)
205-
206201
// Use prev data if an isDataEqual function is defined and returns `true`
207202
if (this.options.isDataEqual?.(prevData, data)) {
208203
data = prevData as TData
@@ -438,11 +433,41 @@ export class Query<
438433
this.dispatch({ type: 'fetch', meta: context.fetchOptions?.meta })
439434
}
440435

436+
const onError = (error: TError | { silent?: boolean }) => {
437+
// Optimistically update state if needed
438+
if (!(isCancelledError(error) && error.silent)) {
439+
this.dispatch({
440+
type: 'error',
441+
error: error as TError,
442+
})
443+
}
444+
445+
if (!isCancelledError(error)) {
446+
// Notify cache callback
447+
this.cache.config.onError?.(error, this as Query<any, any, any, any>)
448+
449+
if (process.env.NODE_ENV !== 'production') {
450+
getLogger().error(error)
451+
}
452+
}
453+
454+
if (!this.isFetchingOptimistic) {
455+
// Schedule query gc after fetching
456+
this.scheduleGc()
457+
}
458+
this.isFetchingOptimistic = false
459+
}
460+
441461
// Try to fetch the data
442462
this.retryer = createRetryer({
443463
fn: context.fetchFn as () => TData,
444464
abort: abortController?.abort?.bind(abortController),
445465
onSuccess: data => {
466+
if (typeof data === 'undefined') {
467+
onError(new Error('Query data cannot be undefined') as any)
468+
return
469+
}
470+
446471
this.setData(data as TData)
447472

448473
// Notify cache callback
@@ -454,30 +479,7 @@ export class Query<
454479
}
455480
this.isFetchingOptimistic = false
456481
},
457-
onError: (error: TError | { silent?: boolean }) => {
458-
// Optimistically update state if needed
459-
if (!(isCancelledError(error) && error.silent)) {
460-
this.dispatch({
461-
type: 'error',
462-
error: error as TError,
463-
})
464-
}
465-
466-
if (!isCancelledError(error)) {
467-
// Notify cache callback
468-
this.cache.config.onError?.(error, this as Query<any, any, any, any>)
469-
470-
if (process.env.NODE_ENV !== 'production') {
471-
getLogger().error(error)
472-
}
473-
}
474-
475-
if (!this.isFetchingOptimistic) {
476-
// Schedule query gc after fetching
477-
this.scheduleGc()
478-
}
479-
this.isFetchingOptimistic = false
480-
},
482+
onError,
481483
onFail: () => {
482484
this.dispatch({ type: 'failed' })
483485
},

src/core/queryClient.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
partialMatchKey,
99
hashQueryKeyByOptions,
1010
MutationFilters,
11+
functionalUpdate,
1112
} from './utils'
1213
import type {
1314
QueryClientConfig,
@@ -125,14 +126,22 @@ export class QueryClient {
125126

126127
setQueryData<TData>(
127128
queryKey: QueryKey,
128-
updater: Updater<TData | undefined, TData>,
129+
updater: Updater<TData | undefined, TData> | undefined,
129130
options?: SetDataOptions
130-
): TData {
131+
): TData | undefined {
132+
const query = this.queryCache.find<TData>(queryKey)
133+
const prevData = query?.state.data
134+
const data = functionalUpdate(updater, prevData)
135+
136+
if (typeof data === 'undefined') {
137+
return undefined
138+
}
139+
131140
const parsedOptions = parseQueryArgs(queryKey)
132141
const defaultedOptions = this.defaultQueryOptions(parsedOptions)
133142
return this.queryCache
134143
.build(this, defaultedOptions)
135-
.setData(updater, { ...options, notifySuccess: false })
144+
.setData(data, { ...options, notifySuccess: false })
136145
}
137146

138147
setQueriesData<TData>(
@@ -151,7 +160,7 @@ export class QueryClient {
151160
queryKeyOrFilters: QueryKey | QueryFilters,
152161
updater: Updater<TData | undefined, TData>,
153162
options?: SetDataOptions
154-
): [QueryKey, TData][] {
163+
): [QueryKey, TData | undefined][] {
155164
return notifyManager.batch(() =>
156165
this.getQueryCache()
157166
.findAll(queryKeyOrFilters)

src/core/tests/query.test.tsx

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
isError,
1313
onlineManager,
1414
QueryFunctionContext,
15+
QueryObserverResult,
1516
} from '../..'
1617
import { waitFor } from '@testing-library/react'
1718

@@ -787,6 +788,7 @@ describe('query', () => {
787788
let signalTest: any
788789
await queryClient.prefetchQuery(key, ({ signal }) => {
789790
signalTest = signal
791+
return 'data'
790792
})
791793

792794
expect(signalTest).toBeUndefined()
@@ -814,6 +816,31 @@ describe('query', () => {
814816
consoleMock.mockRestore()
815817
})
816818

819+
test('fetch should dispatch an error if the queryFn returns undefined', async () => {
820+
const key = queryKey()
821+
822+
const observer = new QueryObserver(queryClient, {
823+
queryKey: key,
824+
queryFn: (() => undefined) as any,
825+
retry: false,
826+
})
827+
828+
let observerResult: QueryObserverResult<unknown, unknown> | undefined
829+
830+
const unsubscribe = observer.subscribe(result => {
831+
observerResult = result
832+
})
833+
834+
await sleep(10)
835+
836+
expect(observerResult).toMatchObject({
837+
isError: true,
838+
error: new Error('Query data cannot be undefined'),
839+
})
840+
841+
unsubscribe()
842+
})
843+
817844
test('fetch should dispatch fetch if is fetching and current promise is undefined', async () => {
818845
const key = queryKey()
819846

0 commit comments

Comments
 (0)