Skip to content

Commit 3b2c408

Browse files
authored
feat(useQuery) transform placeholder data with select (TanStack#2108)
* feat(useQuery): run placeholderData through select * feat(useQuery): run placeholderData through select add a test for placeholderDataFunction * feat(useQuery): run placeholderData through select fix structural sharing, in the options, we might also have the placeholderData function, but on the previous result, we have the correct result from the previous iteration, which we want to keep if possible
1 parent c932d00 commit 3b2c408

File tree

3 files changed

+110
-3
lines changed

3 files changed

+110
-3
lines changed

src/core/queryObserver.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -507,13 +507,31 @@ export class QueryObserver<
507507
} else {
508508
placeholderData =
509509
typeof options.placeholderData === 'function'
510-
? (options.placeholderData as PlaceholderDataFunction<TData>)()
510+
? (options.placeholderData as PlaceholderDataFunction<TQueryData>)()
511511
: options.placeholderData
512+
if (options.select && typeof placeholderData !== 'undefined') {
513+
try {
514+
placeholderData = options.select(placeholderData)
515+
if (options.structuralSharing !== false) {
516+
placeholderData = replaceEqualDeep(
517+
prevResult?.data,
518+
placeholderData
519+
)
520+
}
521+
this.previousSelectError = null
522+
} catch (selectError) {
523+
getLogger().error(selectError)
524+
error = selectError
525+
this.previousSelectError = selectError
526+
errorUpdatedAt = Date.now()
527+
status = 'error'
528+
}
529+
}
512530
}
513531

514532
if (typeof placeholderData !== 'undefined') {
515533
status = 'success'
516-
data = placeholderData
534+
data = placeholderData as TData
517535
isPlaceholderData = true
518536
}
519537
}

src/core/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ export interface QueryObserverOptions<
184184
/**
185185
* If set, this value will be used as the placeholder data for this particular query observer while the query is still in the `loading` data and no initialData has been provided.
186186
*/
187-
placeholderData?: TData | PlaceholderDataFunction<TData>
187+
placeholderData?: TQueryData | PlaceholderDataFunction<TQueryData>
188188
/**
189189
* If set, the observer will optimistically set the result in fetching state before the query has actually started fetching.
190190
* This is to make sure the results are not lagging behind.

src/react/tests/useQuery.test.tsx

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3462,6 +3462,95 @@ describe('useQuery', () => {
34623462
])
34633463
})
34643464

3465+
it('placeholder data should run through select', async () => {
3466+
const key1 = queryKey()
3467+
3468+
const states: UseQueryResult<string>[] = []
3469+
3470+
function Page() {
3471+
const state = useQuery(key1, () => 1, {
3472+
placeholderData: 23,
3473+
select: data => String(data * 2),
3474+
})
3475+
3476+
states.push(state)
3477+
3478+
return (
3479+
<div>
3480+
<h2>Data: {state.data}</h2>
3481+
<div>Status: {state.status}</div>
3482+
</div>
3483+
)
3484+
}
3485+
3486+
const rendered = renderWithClient(queryClient, <Page />)
3487+
await waitFor(() => rendered.getByText('Data: 2'))
3488+
3489+
expect(states).toMatchObject([
3490+
{
3491+
isSuccess: true,
3492+
isPlaceholderData: true,
3493+
data: '46',
3494+
},
3495+
{
3496+
isSuccess: true,
3497+
isPlaceholderData: false,
3498+
data: '2',
3499+
},
3500+
])
3501+
})
3502+
3503+
it('placeholder data function result should run through select', async () => {
3504+
const key1 = queryKey()
3505+
3506+
const states: UseQueryResult<string>[] = []
3507+
let placeholderFunctionRunCount = 0
3508+
3509+
function Page() {
3510+
const state = useQuery(key1, () => 1, {
3511+
placeholderData: () => {
3512+
placeholderFunctionRunCount++
3513+
return 23
3514+
},
3515+
select: data => String(data * 2),
3516+
})
3517+
3518+
states.push(state)
3519+
3520+
return (
3521+
<div>
3522+
<h2>Data: {state.data}</h2>
3523+
<div>Status: {state.status}</div>
3524+
</div>
3525+
)
3526+
}
3527+
3528+
const rendered = renderWithClient(queryClient, <Page />)
3529+
await waitFor(() => rendered.getByText('Data: 2'))
3530+
3531+
rendered.rerender(<Page />)
3532+
3533+
expect(states).toMatchObject([
3534+
{
3535+
isSuccess: true,
3536+
isPlaceholderData: true,
3537+
data: '46',
3538+
},
3539+
{
3540+
isSuccess: true,
3541+
isPlaceholderData: false,
3542+
data: '2',
3543+
},
3544+
{
3545+
isSuccess: true,
3546+
isPlaceholderData: false,
3547+
data: '2',
3548+
},
3549+
])
3550+
3551+
expect(placeholderFunctionRunCount).toEqual(1)
3552+
})
3553+
34653554
it('should cancel the query function when there are no more subscriptions', async () => {
34663555
const key = queryKey()
34673556
let cancelFn: jest.Mock = jest.fn()

0 commit comments

Comments
 (0)