diff --git a/docs/src/pages/reference/useQuery.md b/docs/src/pages/reference/useQuery.md index aeffd9e7e01..c3703a472d3 100644 --- a/docs/src/pages/reference/useQuery.md +++ b/docs/src/pages/reference/useQuery.md @@ -33,6 +33,7 @@ const { initialDataUpdatedAt isDataEqual, keepPreviousData, + meta, notifyOnChangeProps, notifyOnChangePropsExclusions, onError, @@ -177,6 +178,9 @@ const result = useQuery({ - Set this to `true` if you want errors to be thrown in the render phase and propagate to the nearest error boundary - Set this to `false` to disable `suspense`'s default behaviour of throwing errors to the error boundary. - If set to a function, it will be passed the error and should return a boolean indicating whether to show the error in an error boundary (`true`) or return the error as state (`false`) +- `meta: Record` + - 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`. **Returns** diff --git a/src/core/infiniteQueryBehavior.ts b/src/core/infiniteQueryBehavior.ts index b6a95380958..502621bcae5 100644 --- a/src/core/infiniteQueryBehavior.ts +++ b/src/core/infiniteQueryBehavior.ts @@ -60,6 +60,7 @@ export function infiniteQueryBehavior< const queryFnContext: QueryFunctionContext = { queryKey: context.queryKey, pageParam: param, + meta: context.meta, } const queryFnResult = queryFn(queryFnContext) diff --git a/src/core/query.ts b/src/core/query.ts index 76e92dde31d..4fde0c12c53 100644 --- a/src/core/query.ts +++ b/src/core/query.ts @@ -14,6 +14,7 @@ import type { QueryStatus, QueryFunctionContext, EnsuredQueryKey, + QueryMeta, } from './types' import type { QueryCache } from './queryCache' import type { QueryObserver } from './queryObserver' @@ -35,6 +36,7 @@ interface QueryConfig< options?: QueryOptions defaultOptions?: QueryOptions state?: QueryState + meta: QueryMeta | undefined } export interface QueryState { @@ -63,6 +65,7 @@ export interface FetchContext< options: QueryOptions queryKey: EnsuredQueryKey state: QueryState + meta: QueryMeta | undefined } export interface QueryBehavior< @@ -152,6 +155,7 @@ export class Query< revertState?: QueryState state: QueryState cacheTime!: number + meta: QueryMeta | undefined private cache: QueryCache private promise?: Promise @@ -169,6 +173,7 @@ export class Query< this.queryHash = config.queryHash this.initialState = config.state || this.getDefaultState(this.options) this.state = this.initialState + this.meta = config.meta this.scheduleGc() } @@ -177,6 +182,8 @@ export class Query< ): void { this.options = { ...this.defaultOptions, ...options } + this.meta = options?.meta + // Default to 5 minutes if not cache time is set this.cacheTime = Math.max( this.cacheTime || 0, @@ -388,6 +395,7 @@ export class Query< const queryFnContext: QueryFunctionContext = { queryKey, pageParam: undefined, + meta: this.meta, } // Create fetch function @@ -403,6 +411,7 @@ export class Query< queryKey: queryKey, state: this.state, fetchFn, + meta: this.meta, } if (this.options.behavior?.onFetch) { diff --git a/src/core/queryCache.ts b/src/core/queryCache.ts index 9f62f65c7a6..1a72d194be5 100644 --- a/src/core/queryCache.ts +++ b/src/core/queryCache.ts @@ -98,6 +98,7 @@ export class QueryCache extends Subscribable { options: client.defaultQueryOptions(options), state, defaultOptions: client.getQueryDefaults(queryKey), + meta: options.meta, }) this.add(query) } diff --git a/src/core/tests/infiniteQueryObserver.test.tsx b/src/core/tests/infiniteQueryObserver.test.tsx index d20834b8d8a..e0fff2a28b8 100644 --- a/src/core/tests/infiniteQueryObserver.test.tsx +++ b/src/core/tests/infiniteQueryObserver.test.tsx @@ -33,4 +33,32 @@ describe('InfiniteQueryObserver', () => { data: { pages: ['1'], pageParams: [undefined] }, }) }) + + test('InfiniteQueryObserver should pass the meta option to the queryFn', async () => { + const meta = { + it: 'works', + } + + const key = queryKey() + const queryFn = jest.fn(() => 1) + const observer = new InfiniteQueryObserver(queryClient, { + meta, + queryKey: key, + queryFn, + select: data => ({ + pages: data.pages.map(x => `${x}`), + pageParams: data.pageParams, + }), + }) + let observerResult + const unsubscribe = observer.subscribe(result => { + observerResult = result + }) + await sleep(1) + unsubscribe() + expect(observerResult).toMatchObject({ + data: { pages: ['1'], pageParams: [undefined] }, + }) + expect(queryFn).toBeCalledWith(expect.objectContaining({ meta })) + }) }) diff --git a/src/core/tests/query.test.tsx b/src/core/tests/query.test.tsx index a9883c205c1..8801e8b58cb 100644 --- a/src/core/tests/query.test.tsx +++ b/src/core/tests/query.test.tsx @@ -472,4 +472,63 @@ describe('query', () => { unsubscribe1() expect(query?.getObserversCount()).toEqual(0) }) + + test('stores meta object in query', async () => { + const meta = { + it: 'works', + } + + const key = queryKey() + + await queryClient.prefetchQuery(key, () => 'data', { + meta, + }) + + const query = queryCache.find(key)! + + expect(query.meta).toBe(meta) + expect(query.options.meta).toBe(meta) + }) + + test('updates meta object on change', async () => { + const meta = { + it: 'works', + } + + const key = queryKey() + const queryFn = () => 'data' + + await queryClient.prefetchQuery(key, queryFn, { + meta, + }) + + await queryClient.prefetchQuery(key, queryFn, { + meta: undefined, + }) + + const query = queryCache.find(key)! + + expect(query.meta).toBeUndefined() + expect(query.options.meta).toBeUndefined() + }) + + test('provides meta object inside query function', async () => { + const meta = { + it: 'works', + } + + const queryFn = jest.fn(() => 'data') + + const key = queryKey() + + await queryClient.prefetchQuery(key, queryFn, { + meta, + }) + + expect(queryFn).toBeCalledWith( + expect.objectContaining({ + meta, + }) + ) + }) }) diff --git a/src/core/types.ts b/src/core/types.ts index 8e461c5deb1..464dab1e79a 100644 --- a/src/core/types.ts +++ b/src/core/types.ts @@ -19,6 +19,7 @@ export interface QueryFunctionContext< > { queryKey: EnsuredQueryKey pageParam?: TPageParam + meta: QueryMeta | undefined } export type InitialDataFunction = () => T | undefined @@ -44,6 +45,8 @@ export interface InfiniteData { pageParams: unknown[] } +export type QueryMeta = Record + export interface QueryOptions< TQueryFnData = unknown, TError = unknown, @@ -83,6 +86,11 @@ export interface QueryOptions< */ getNextPageParam?: GetNextPageParamFunction _defaulted?: boolean + /** + * Additional payload to be stored on each query. + * Use this property to pass information that can be used in other places. + */ + meta?: QueryMeta } export interface QueryObserverOptions<