Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .changeset/placeholder-data-context.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
'@tanstack/angular-query-experimental': minor
'@tanstack/preact-query': minor
'@tanstack/query-core': minor
'@tanstack/react-query': minor
'@tanstack/solid-query': minor
'@tanstack/svelte-query': minor
---

`placeholderData` functions now receive a third argument holding the `client`, the `queryKey` and the `meta` of the Query, so a `queryOptions` factory can seed from the cache without being given a `QueryClient`.
19 changes: 19 additions & 0 deletions docs/framework/angular/guides/placeholder-query-data.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,5 +54,24 @@ export class BlogPostComponent {
```

[//]: # 'ExampleCache'
[//]: # 'ExampleCacheContext'

```ts
@Injectable({
providedIn: 'root',
})
export class BlogPostsService {
blogPost(blogPostId: number) {
return queryOptions({
queryKey: ['blogPost', blogPostId],
queryFn: () => fetch(`/blogPosts/${blogPostId}`),
placeholderData: (_previousData, _previousQuery, { client }) =>
client.getQueryData(['blogPosts'])?.find((d) => d.id === blogPostId),
})
}
}
```

[//]: # 'ExampleCacheContext'
[//]: # 'Materials'
[//]: # 'Materials'
23 changes: 23 additions & 0 deletions docs/framework/react/guides/placeholder-query-data.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,29 @@ function BlogPost({ blogPostId }) {
```

[//]: # 'ExampleCache'

### Placeholder Data in a Query Options Factory

The example above needs the `queryClient` to be available where the options are defined, which is not the case when you extract them into a [`queryOptions`](./query-options.md) factory. To cover that, the `placeholderData` function receives a third argument holding the `client`, the `queryKey` and the `meta` of the Query:

[//]: # 'ExampleCacheContext'

```tsx
export const blogPostOptions = (blogPostId: string) =>
queryOptions({
queryKey: ['blogPost', blogPostId],
queryFn: () => fetch(`/blogPosts/${blogPostId}`),
placeholderData: (_previousData, _previousQuery, { client }) =>
client.getQueryData(['blogPosts'])?.find((d) => d.id === blogPostId),
})
```

[//]: # 'ExampleCacheContext'

Note that the property is named `client`, not `queryClient`, to match the [`QueryFunctionContext`](./query-functions.md#queryfunctioncontext) given to the `queryFn`.

> `placeholderData` runs during render, so only read from the `client` here - do not write to the cache with `setQueryData` or trigger fetches with `invalidateQueries`.

[//]: # 'Materials'

## Further reading
Expand Down
2 changes: 1 addition & 1 deletion docs/framework/react/reference/useQueries.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ The `useQueries` hook accepts an options object with a **queries** key whose val

**placeholderData**

The `placeholderData` option exists for `useQueries` as well, but it doesn't get information passed from previously rendered Queries like `useQuery` does, because the input to `useQueries` can be a different number of Queries on each render.
The `placeholderData` option exists for `useQueries` as well, but it doesn't get information passed from previously rendered Queries like `useQuery` does, because the input to `useQueries` can be a different number of Queries on each render. It does still receive the `PlaceholderDataContext` as its third argument, so the `client`, the `queryKey` and the `meta` of each Query are available.

**Returns**

Expand Down
6 changes: 3 additions & 3 deletions docs/framework/react/reference/useQuery.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,11 +157,11 @@ const {
- `initialDataUpdatedAt: number | (() => number | undefined)`
- Optional
- If set, this value will be used as the time (in milliseconds) of when the `initialData` itself was last updated.
- `placeholderData: TData | (previousValue: TData | undefined, previousQuery: Query | undefined) => TData`
- `placeholderData: TData | (previousValue: TData | undefined, previousQuery: Query | undefined, context: PlaceholderDataContext) => TData`
- Optional
- If set, this value will be used as the placeholder data for this particular query observer while the query is still in the `pending` state.
- `placeholderData` is **not persisted** to the cache
- If you provide a function for `placeholderData`, as a first argument you will receive previously watched query data if available, and the second argument will be the complete previousQuery instance.
- If you provide a function for `placeholderData`, as a first argument you will receive previously watched query data if available, the second argument will be the complete previousQuery instance, and the third argument will be a `PlaceholderDataContext` with the `client`, the `queryKey` and the `meta` of this query.
- `structuralSharing: boolean | (oldData: unknown | undefined, newData: unknown) => unknown`
- Optional
- Defaults to `true`
Expand All @@ -177,7 +177,7 @@ const {
- If set to a function, it will be passed the error and the query, and it should return a boolean indicating whether to show the error in an error boundary (`true`) or return the error as state (`false`)
- `meta: Record<string, unknown>`
- Optional
- If set, stores additional information on the query cache entry that can be used as needed. It will be accessible wherever the `query` is available, and is also part of the `QueryFunctionContext` provided to the `queryFn`.
- If set, stores additional information on the query cache entry that can be used as needed. It will be accessible wherever the `query` is available, and is also part of the `QueryFunctionContext` provided to the `queryFn` and of the `PlaceholderDataContext` provided to a `placeholderData` function.

**Parameter2 (QueryClient)**

Expand Down
6 changes: 3 additions & 3 deletions docs/framework/solid/reference/useQuery.md
Original file line number Diff line number Diff line change
Expand Up @@ -205,11 +205,11 @@ function App() {
- Optional
- This option can be used to transform or select a part of the data returned by the query function. It affects the returned `data` value, but does not affect what gets stored in the query cache.
- The `select` function will only run if `data` changed, or if the reference to the `select` function itself changes. To optimize, wrap the function in `useCallback`.
- ##### `placeholderData: TData | (previousValue: TData | undefined; previousQuery: Query | undefined,) => TData`
- ##### `placeholderData: TData | (previousValue: TData | undefined, previousQuery: Query | undefined, context: PlaceholderDataContext) => TData`
- Optional
- If set, this value will be used as the placeholder data for this particular query observer while the query is still in the `pending` state.
- `placeholderData` is **not persisted** to the cache
- If you provide a function for `placeholderData`, as a first argument you will receive previously watched query data if available, and the second argument will be the complete previousQuery instance.
- If you provide a function for `placeholderData`, as a first argument you will receive previously watched query data if available, the second argument will be the complete previousQuery instance, and the third argument will be a `PlaceholderDataContext` with the `client`, the `queryKey` and the `meta` of this query.
- ##### `deferStream: boolean`
- Optional
- Defaults to `false`
Expand Down Expand Up @@ -241,7 +241,7 @@ function App() {
- If set, this value will be used as the time (in milliseconds) of when the `initialData` itself was last updated.
- ##### `meta: Record<string, unknown>`
- Optional
- If set, stores additional information on the query cache entry that can be used as needed. It will be accessible wherever the `query` is available, and is also part of the `QueryFunctionContext` provided to the `queryFn`.
- If set, stores additional information on the query cache entry that can be used as needed. It will be accessible wherever the `query` is available, and is also part of the `QueryFunctionContext` provided to the `queryFn` and of the `PlaceholderDataContext` provided to a `placeholderData` function.
- ##### `queryKeyHashFn: (queryKey: QueryKey) => string`
- Optional
- If specified, this function is used to hash the `queryKey` to a string.
Expand Down
4 changes: 3 additions & 1 deletion packages/angular-query-experimental/src/inject-queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ type QueryObserverOptionsForCreateQueries<
CreateQueryOptions<TQueryFnData, TError, TData, TQueryKey>,
'placeholderData'
> & {
placeholderData?: TQueryFnData | QueriesPlaceholderDataFunction<TQueryFnData>
placeholderData?:
| TQueryFnData
| QueriesPlaceholderDataFunction<TQueryFnData, TQueryKey>
}

// Avoid TS depth-limit error in case of large array literal
Expand Down
4 changes: 3 additions & 1 deletion packages/preact-query/src/useQueries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,9 @@ type UseQueryOptionsForUseQueries<
UseQueryOptions<TQueryFnData, TError, TData, TQueryKey>,
'placeholderData' | 'subscribed'
> & {
placeholderData?: TQueryFnData | QueriesPlaceholderDataFunction<TQueryFnData>
placeholderData?:
| TQueryFnData
| QueriesPlaceholderDataFunction<TQueryFnData, TQueryKey>
}

// Avoid TS depth-limit error in case of large array literal
Expand Down
53 changes: 51 additions & 2 deletions packages/query-core/src/__tests__/queryObserver.test-d.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { afterEach, beforeEach, describe, expectTypeOf, it } from 'vitest'
import { queryKey } from '@tanstack/query-test-utils'
import { QueryClient, QueryObserver } from '..'
import type { DefaultError } from '..'
import { QueryClient, QueryObserver, keepPreviousData } from '..'
import type { DefaultError, QueryMeta } from '..'

describe('queryObserver', () => {
let queryClient: QueryClient
Expand Down Expand Up @@ -149,5 +149,54 @@ describe('queryObserver', () => {
},
})
})

it('context should have a typed queryKey', () => {
const testQueryKey = ['SomeQuery', 42, { foo: 'bar' }] as const

new QueryObserver(new QueryClient(), {
queryKey: testQueryKey,
placeholderData: (_previousData, _previousQuery, context) => {
expectTypeOf(context.queryKey).toEqualTypeOf<typeof testQueryKey>()
return undefined
},
})
})

it('context should have a typed client', () => {
new QueryObserver(new QueryClient(), {
queryKey: queryKey(),
placeholderData: (_previousData, _previousQuery, context) => {
expectTypeOf(context.client).toEqualTypeOf<QueryClient>()
return undefined
},
})
})

it('context should have a typed meta', () => {
new QueryObserver(new QueryClient(), {
queryKey: queryKey(),
placeholderData: (_previousData, _previousQuery, context) => {
expectTypeOf(context.meta).toEqualTypeOf<QueryMeta | undefined>()
return undefined
},
})
})

it('should accept a function that omits the context parameter', () => {
new QueryObserver(new QueryClient(), {
queryKey: queryKey(),
queryFn: () => 'data',
placeholderData: (previousData) => {
expectTypeOf(previousData).toEqualTypeOf<string | undefined>()
return previousData
},
})

new QueryObserver(new QueryClient(), {
queryKey: queryKey(),
queryFn: () => 'data',
placeholderData: keepPreviousData,
})
})
})
})
Loading