Skip to content

Commit 1c8a921

Browse files
authored
fix(query-core): update initialData when an observer mounts while a Query without data exists (#9620)
initialData gives the guarantee that data cannot be undefined, but this falls short when you create a Query via prefetching, and then mount the observer that has initialData set. That's because initialData is only doing something in the constructor of the Query, so if the Observer isn't the one who creates the Query, it does nothing This fix makes sure that when new options are applied on a Query (which e.g. happens when an observer mounts), initialData is taken into account when the Query doesn't have any data yet
1 parent b43af57 commit 1c8a921

File tree

2 files changed

+74
-0
lines changed

2 files changed

+74
-0
lines changed

packages/query-core/src/__tests__/query.test.tsx

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1193,6 +1193,68 @@ describe('query', () => {
11931193
expect(query.state.data).toBe('initial data')
11941194
})
11951195

1196+
test('should update initialData when Query exists without data', async () => {
1197+
const key = queryKey()
1198+
const queryFn = vi.fn(async () => {
1199+
await sleep(100)
1200+
return 'data'
1201+
})
1202+
1203+
const promise = queryClient.prefetchQuery({
1204+
queryKey: key,
1205+
queryFn,
1206+
staleTime: 1000,
1207+
})
1208+
1209+
vi.advanceTimersByTime(50)
1210+
1211+
expect(queryClient.getQueryState(key)).toMatchObject({
1212+
data: undefined,
1213+
status: 'pending',
1214+
fetchStatus: 'fetching',
1215+
})
1216+
1217+
const observer = new QueryObserver(queryClient, {
1218+
queryKey: key,
1219+
queryFn,
1220+
staleTime: 1000,
1221+
initialData: 'initialData',
1222+
initialDataUpdatedAt: 10,
1223+
})
1224+
1225+
const unsubscribe = observer.subscribe(vi.fn())
1226+
1227+
expect(queryClient.getQueryState(key)).toMatchObject({
1228+
data: 'initialData',
1229+
dataUpdatedAt: 10,
1230+
status: 'success',
1231+
fetchStatus: 'fetching',
1232+
})
1233+
1234+
vi.advanceTimersByTime(50)
1235+
1236+
await promise
1237+
1238+
expect(queryClient.getQueryState(key)).toMatchObject({
1239+
data: 'data',
1240+
status: 'success',
1241+
fetchStatus: 'idle',
1242+
})
1243+
1244+
expect(queryFn).toHaveBeenCalledTimes(1)
1245+
1246+
unsubscribe()
1247+
1248+
// resetting should get us back ot 'initialData'
1249+
queryClient.getQueryCache().find({ queryKey: key })!.reset()
1250+
1251+
expect(queryClient.getQueryState(key)).toMatchObject({
1252+
data: 'initialData',
1253+
status: 'success',
1254+
fetchStatus: 'idle',
1255+
})
1256+
})
1257+
11961258
test('should not override fetching state when revert happens after new observer subscribes', async () => {
11971259
const key = queryKey()
11981260
let count = 0

packages/query-core/src/query.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,18 @@ export class Query<
205205
this.options = { ...this.#defaultOptions, ...options }
206206

207207
this.updateGcTime(this.options.gcTime)
208+
209+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
210+
if (this.state && this.state.data === undefined) {
211+
const defaultState = getDefaultState(this.options)
212+
if (defaultState.data !== undefined) {
213+
this.setData(defaultState.data, {
214+
updatedAt: defaultState.dataUpdatedAt,
215+
manual: true,
216+
})
217+
this.#initialState = defaultState
218+
}
219+
}
208220
}
209221

210222
protected optionalRemove() {

0 commit comments

Comments
 (0)