Skip to content

Commit 4472bae

Browse files
authored
feat(useQuery): add meta field (#2818)
1 parent 4d762a4 commit 4472bae

File tree

7 files changed

+110
-0
lines changed

7 files changed

+110
-0
lines changed

docs/src/pages/reference/useQuery.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ const {
3333
initialDataUpdatedAt
3434
isDataEqual,
3535
keepPreviousData,
36+
meta,
3637
notifyOnChangeProps,
3738
notifyOnChangePropsExclusions,
3839
onError,
@@ -177,6 +178,9 @@ const result = useQuery({
177178
- Set this to `true` if you want errors to be thrown in the render phase and propagate to the nearest error boundary
178179
- Set this to `false` to disable `suspense`'s default behaviour of throwing errors to the error boundary.
179180
- 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`)
181+
- `meta: Record<string, unknown>`
182+
- Optional
183+
- 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`.
180184
181185
**Returns**
182186

src/core/infiniteQueryBehavior.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ export function infiniteQueryBehavior<
6060
const queryFnContext: QueryFunctionContext = {
6161
queryKey: context.queryKey,
6262
pageParam: param,
63+
meta: context.meta,
6364
}
6465

6566
const queryFnResult = queryFn(queryFnContext)

src/core/query.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import type {
1414
QueryStatus,
1515
QueryFunctionContext,
1616
EnsuredQueryKey,
17+
QueryMeta,
1718
} from './types'
1819
import type { QueryCache } from './queryCache'
1920
import type { QueryObserver } from './queryObserver'
@@ -35,6 +36,7 @@ interface QueryConfig<
3536
options?: QueryOptions<TQueryFnData, TError, TData, TQueryKey>
3637
defaultOptions?: QueryOptions<TQueryFnData, TError, TData, TQueryKey>
3738
state?: QueryState<TData, TError>
39+
meta: QueryMeta | undefined
3840
}
3941

4042
export interface QueryState<TData = unknown, TError = unknown> {
@@ -63,6 +65,7 @@ export interface FetchContext<
6365
options: QueryOptions<TQueryFnData, TError, TData, any>
6466
queryKey: EnsuredQueryKey<TQueryKey>
6567
state: QueryState<TData, TError>
68+
meta: QueryMeta | undefined
6669
}
6770

6871
export interface QueryBehavior<
@@ -152,6 +155,7 @@ export class Query<
152155
revertState?: QueryState<TData, TError>
153156
state: QueryState<TData, TError>
154157
cacheTime!: number
158+
meta: QueryMeta | undefined
155159

156160
private cache: QueryCache
157161
private promise?: Promise<TData>
@@ -169,6 +173,7 @@ export class Query<
169173
this.queryHash = config.queryHash
170174
this.initialState = config.state || this.getDefaultState(this.options)
171175
this.state = this.initialState
176+
this.meta = config.meta
172177
this.scheduleGc()
173178
}
174179

@@ -177,6 +182,8 @@ export class Query<
177182
): void {
178183
this.options = { ...this.defaultOptions, ...options }
179184

185+
this.meta = options?.meta
186+
180187
// Default to 5 minutes if not cache time is set
181188
this.cacheTime = Math.max(
182189
this.cacheTime || 0,
@@ -388,6 +395,7 @@ export class Query<
388395
const queryFnContext: QueryFunctionContext<TQueryKey> = {
389396
queryKey,
390397
pageParam: undefined,
398+
meta: this.meta,
391399
}
392400

393401
// Create fetch function
@@ -403,6 +411,7 @@ export class Query<
403411
queryKey: queryKey,
404412
state: this.state,
405413
fetchFn,
414+
meta: this.meta,
406415
}
407416

408417
if (this.options.behavior?.onFetch) {

src/core/queryCache.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ export class QueryCache extends Subscribable<QueryCacheListener> {
9898
options: client.defaultQueryOptions(options),
9999
state,
100100
defaultOptions: client.getQueryDefaults(queryKey),
101+
meta: options.meta,
101102
})
102103
this.add(query)
103104
}

src/core/tests/infiniteQueryObserver.test.tsx

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,32 @@ describe('InfiniteQueryObserver', () => {
3333
data: { pages: ['1'], pageParams: [undefined] },
3434
})
3535
})
36+
37+
test('InfiniteQueryObserver should pass the meta option to the queryFn', async () => {
38+
const meta = {
39+
it: 'works',
40+
}
41+
42+
const key = queryKey()
43+
const queryFn = jest.fn(() => 1)
44+
const observer = new InfiniteQueryObserver(queryClient, {
45+
meta,
46+
queryKey: key,
47+
queryFn,
48+
select: data => ({
49+
pages: data.pages.map(x => `${x}`),
50+
pageParams: data.pageParams,
51+
}),
52+
})
53+
let observerResult
54+
const unsubscribe = observer.subscribe(result => {
55+
observerResult = result
56+
})
57+
await sleep(1)
58+
unsubscribe()
59+
expect(observerResult).toMatchObject({
60+
data: { pages: ['1'], pageParams: [undefined] },
61+
})
62+
expect(queryFn).toBeCalledWith(expect.objectContaining({ meta }))
63+
})
3664
})

src/core/tests/query.test.tsx

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -472,4 +472,63 @@ describe('query', () => {
472472
unsubscribe1()
473473
expect(query?.getObserversCount()).toEqual(0)
474474
})
475+
476+
test('stores meta object in query', async () => {
477+
const meta = {
478+
it: 'works',
479+
}
480+
481+
const key = queryKey()
482+
483+
await queryClient.prefetchQuery(key, () => 'data', {
484+
meta,
485+
})
486+
487+
const query = queryCache.find(key)!
488+
489+
expect(query.meta).toBe(meta)
490+
expect(query.options.meta).toBe(meta)
491+
})
492+
493+
test('updates meta object on change', async () => {
494+
const meta = {
495+
it: 'works',
496+
}
497+
498+
const key = queryKey()
499+
const queryFn = () => 'data'
500+
501+
await queryClient.prefetchQuery(key, queryFn, {
502+
meta,
503+
})
504+
505+
await queryClient.prefetchQuery(key, queryFn, {
506+
meta: undefined,
507+
})
508+
509+
const query = queryCache.find(key)!
510+
511+
expect(query.meta).toBeUndefined()
512+
expect(query.options.meta).toBeUndefined()
513+
})
514+
515+
test('provides meta object inside query function', async () => {
516+
const meta = {
517+
it: 'works',
518+
}
519+
520+
const queryFn = jest.fn(() => 'data')
521+
522+
const key = queryKey()
523+
524+
await queryClient.prefetchQuery(key, queryFn, {
525+
meta,
526+
})
527+
528+
expect(queryFn).toBeCalledWith(
529+
expect.objectContaining({
530+
meta,
531+
})
532+
)
533+
})
475534
})

src/core/types.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export interface QueryFunctionContext<
1919
> {
2020
queryKey: EnsuredQueryKey<TQueryKey>
2121
pageParam?: TPageParam
22+
meta: QueryMeta | undefined
2223
}
2324

2425
export type InitialDataFunction<T> = () => T | undefined
@@ -44,6 +45,8 @@ export interface InfiniteData<TData> {
4445
pageParams: unknown[]
4546
}
4647

48+
export type QueryMeta = Record<string, unknown>
49+
4750
export interface QueryOptions<
4851
TQueryFnData = unknown,
4952
TError = unknown,
@@ -83,6 +86,11 @@ export interface QueryOptions<
8386
*/
8487
getNextPageParam?: GetNextPageParamFunction<TQueryFnData>
8588
_defaulted?: boolean
89+
/**
90+
* Additional payload to be stored on each query.
91+
* Use this property to pass information that can be used in other places.
92+
*/
93+
meta?: QueryMeta
8694
}
8795

8896
export interface QueryObserverOptions<

0 commit comments

Comments
 (0)