Skip to content

Commit b657107

Browse files
ardeoraTkDodo
andauthored
feat(query-core): Add previousQuery to placeholderFn (#5358)
* feat(query-core): Add previousQuery to placeholderFn * Add query-core and react-query tests * Remove unused query type --------- Co-authored-by: Dominik Dorfmeister <[email protected]>
1 parent 0b7235e commit b657107

File tree

4 files changed

+135
-6
lines changed

4 files changed

+135
-6
lines changed

packages/query-core/src/queryObserver.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,9 @@ export class QueryObserver<
6565
#selectError: TError | null
6666
#selectFn?: (data: TQueryData) => TData
6767
#selectResult?: TData
68-
// This property keeps track of the last defined query data.
69-
// It will be used to pass the previous data to the placeholder function between renders.
70-
#lastDefinedQueryData?: TQueryData
68+
// This property keeps track of the last query with defined data.
69+
// It will be used to pass the previous data and query to the placeholder function between renders.
70+
#lastQueryWithDefinedData?: Query<TQueryFnData, TError, TQueryData, TQueryKey>
7171
#staleTimeoutId?: ReturnType<typeof setTimeout>
7272
#refetchIntervalId?: ReturnType<typeof setInterval>
7373
#currentRefetchInterval?: number | false
@@ -489,7 +489,10 @@ export class QueryObserver<
489489
typeof options.placeholderData === 'function'
490490
? (
491491
options.placeholderData as unknown as PlaceholderDataFunction<TQueryData>
492-
)(this.#lastDefinedQueryData)
492+
)(
493+
this.#lastQueryWithDefinedData?.state.data,
494+
this.#lastQueryWithDefinedData as any,
495+
)
493496
: options.placeholderData
494497
if (options.select && typeof placeholderData !== 'undefined') {
495498
try {
@@ -572,7 +575,7 @@ export class QueryObserver<
572575
}
573576

574577
if (this.#currentResultState.data !== undefined) {
575-
this.#lastDefinedQueryData = this.#currentResultState.data
578+
this.#lastQueryWithDefinedData = this.#currentQuery
576579
}
577580
this.#currentResult = nextResult
578581

packages/query-core/src/tests/queryObserver.test.tsx

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -691,6 +691,72 @@ describe('queryObserver', () => {
691691
expect(observer.getCurrentResult().isPlaceholderData).toBe(false)
692692
})
693693

694+
test('should pass the correct previous queryKey (from prevQuery) to placeholderData function params with select', async () => {
695+
const results: QueryObserverResult[] = []
696+
const keys: Array<readonly unknown[] | null> = []
697+
698+
const key1 = queryKey()
699+
const key2 = queryKey()
700+
701+
const data1 = { value: 'data1' }
702+
const data2 = { value: 'data2' }
703+
704+
const observer = new QueryObserver(queryClient, {
705+
queryKey: key1,
706+
queryFn: () => data1,
707+
placeholderData: (prev, prevQuery) => {
708+
keys.push(prevQuery?.queryKey || null)
709+
return prev
710+
},
711+
select: (data) => data.value,
712+
})
713+
714+
const unsubscribe = observer.subscribe((result) => {
715+
results.push(result)
716+
})
717+
718+
await sleep(1)
719+
720+
observer.setOptions({
721+
queryKey: key2,
722+
queryFn: () => data2,
723+
placeholderData: (prev, prevQuery) => {
724+
keys.push(prevQuery?.queryKey || null)
725+
return prev
726+
},
727+
select: (data) => data.value,
728+
})
729+
730+
await sleep(1)
731+
unsubscribe()
732+
expect(results.length).toBe(4)
733+
expect(keys.length).toBe(3)
734+
expect(keys[0]).toBe(null) // First Query - status: 'pending', fetchStatus: 'idle'
735+
expect(keys[1]).toBe(null) // First Query - status: 'pending', fetchStatus: 'fetching'
736+
expect(keys[2]).toBe(key1) // Second Query - status: 'pending', fetchStatus: 'fetching'
737+
738+
expect(results[0]).toMatchObject({
739+
data: undefined,
740+
status: 'pending',
741+
fetchStatus: 'fetching',
742+
}) // Initial fetch
743+
expect(results[1]).toMatchObject({
744+
data: 'data1',
745+
status: 'success',
746+
fetchStatus: 'idle',
747+
}) // Successful fetch
748+
expect(results[2]).toMatchObject({
749+
data: 'data1',
750+
status: 'success',
751+
fetchStatus: 'fetching',
752+
}) // Fetch for new key, but using previous data as placeholder
753+
expect(results[3]).toMatchObject({
754+
data: 'data2',
755+
status: 'success',
756+
fetchStatus: 'idle',
757+
}) // Successful fetch for new key
758+
})
759+
694760
test('should pass the correct previous data to placeholderData function params when select function is used in conjunction', async () => {
695761
const results: QueryObserverResult[] = []
696762

packages/query-core/src/types.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,14 @@ export type InitialDataFunction<T> = () => T | undefined
4949

5050
type NonFunctionGuard<T> = T extends Function ? never : T
5151

52-
export type PlaceholderDataFunction<TQueryData> = (
52+
export type PlaceholderDataFunction<
53+
TQueryFnData = unknown,
54+
TError = DefaultError,
55+
TQueryData = TQueryFnData,
56+
TQueryKey extends QueryKey = QueryKey,
57+
> = (
5358
previousData: TQueryData | undefined,
59+
previousQuery: Query<TQueryFnData, TError, TQueryData, TQueryKey> | undefined,
5460
) => TQueryData | undefined
5561

5662
export type QueriesPlaceholderDataFunction<TQueryData> = () =>

packages/react-query/src/__tests__/useQuery.test.tsx

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1542,6 +1542,60 @@ describe('useQuery', () => {
15421542
})
15431543
})
15441544

1545+
it('should keep the previous queryKey (from prevQuery) between multiple pending queries when placeholderData is set and select fn transform is used', async () => {
1546+
const keys: Array<readonly unknown[] | null> = []
1547+
const key = queryKey()
1548+
const states: UseQueryResult<number>[] = []
1549+
1550+
function Page() {
1551+
const [count, setCount] = React.useState(0)
1552+
1553+
const state = useQuery({
1554+
queryKey: [key, count],
1555+
queryFn: async () => {
1556+
await sleep(10)
1557+
return {
1558+
count,
1559+
}
1560+
},
1561+
select(data) {
1562+
return data.count
1563+
},
1564+
placeholderData: (prevData, prevQuery) => {
1565+
if (prevQuery) {
1566+
keys.push(prevQuery.queryKey)
1567+
}
1568+
return prevData
1569+
},
1570+
})
1571+
1572+
states.push(state)
1573+
1574+
return (
1575+
<div>
1576+
<div>data: {state.data}</div>
1577+
<button onClick={() => setCount((prev) => prev + 1)}>setCount</button>
1578+
</div>
1579+
)
1580+
}
1581+
1582+
const rendered = renderWithClient(queryClient, <Page />)
1583+
1584+
await waitFor(() => rendered.getByText('data: 0'))
1585+
1586+
fireEvent.click(rendered.getByRole('button', { name: 'setCount' }))
1587+
fireEvent.click(rendered.getByRole('button', { name: 'setCount' }))
1588+
fireEvent.click(rendered.getByRole('button', { name: 'setCount' }))
1589+
1590+
await waitFor(() => rendered.getByText('data: 3'))
1591+
1592+
const allPreviousKeysAreTheFirstQueryKey = keys.every(
1593+
(k) => JSON.stringify(k) === JSON.stringify([key, 0]),
1594+
)
1595+
1596+
expect(allPreviousKeysAreTheFirstQueryKey).toBe(true)
1597+
})
1598+
15451599
it('should show placeholderData between multiple pending queries when select fn transform is used', async () => {
15461600
const key = queryKey()
15471601
const states: UseQueryResult<number>[] = []

0 commit comments

Comments
 (0)