Skip to content

Commit

Permalink
feat(core): add client to queryFunctionContext (#8599)
Browse files Browse the repository at this point in the history
* feat: add queryClient to queryFunctionContext

* fix: remove queryClient exception

closing over a queryClient should now error, as we pass the queryClient directly to the queryFunctionContext

* fix: persister

* refactor: keep private reference to cache

* chore: fix tests

* docs: QueryFunctionContext

* refactor: derive from private member

* refactor: name it `client`

* refactor: let's go with client

* chore: try skipping the cache

* chore: re-enable caching

and try to bust it by changing sharedGlobals lol
  • Loading branch information
TkDodo authored Jan 31, 2025
1 parent a507400 commit f86eb7e
Show file tree
Hide file tree
Showing 13 changed files with 37 additions and 40 deletions.
1 change: 1 addition & 0 deletions docs/framework/react/guides/query-functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ function fetchTodoList({ queryKey }) {
The `QueryFunctionContext` is the object passed to each query function. It consists of:

- `queryKey: QueryKey`: [Query Keys](../query-keys)
- `client: QueryClient`: [QueryClient](../../../../reference/QueryClient)
- `signal?: AbortSignal`
- [AbortSignal](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) instance provided by TanStack Query
- Can be used for [Query Cancellation](../query-cancellation)
Expand Down
2 changes: 1 addition & 1 deletion nx.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"sharedGlobals": [
"{workspaceRoot}/.nvmrc",
"{workspaceRoot}/package.json",
"{workspaceRoot}/scripts/getTsupConfig.js",
"{workspaceRoot}/scripts/*.js",
"{workspaceRoot}/tsconfig.json",
"{workspaceRoot}/eslint.config.js"
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,7 @@ describe('injectQuery', () => {
expect(spy).toHaveBeenCalledTimes(2)
// should call queryFn with context containing the new queryKey
expect(spy).toBeCalledWith({
client: queryClient,
meta: undefined,
queryKey: ['key8'],
signal: expect.anything(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -354,24 +354,6 @@ ruleTester.run('exhaustive-deps', rule, {
}
`,
},
{
name: 'should ignore references of the queryClient',
code: `
const CONST_VAL = 1
function useHook() {
const queryClient = useQueryClient()
const queryClient2 = useQueryClient()
useQuery({
queryKey: ["foo"],
queryFn: () => {
doSomething(queryClient)
queryClient.invalidateQueries()
doSomethingSus(queryClient2)
}
});
}
`,
},
{
name: 'query key with nullish coalescing operator',
code: `
Expand Down Expand Up @@ -470,7 +452,7 @@ ruleTester.run('exhaustive-deps', rule, {
queryKey: ['foo'],
queryFn: () => Promise.resolve(EXTERNAL),
}),
};
};
`,
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,7 @@ export const ExhaustiveDepsUtils = {
return (
reference.identifier.name !== 'undefined' &&
reference.identifier.parent.type !== AST_NODE_TYPES.NewExpression &&
!ExhaustiveDepsUtils.isInstanceOfKind(reference.identifier.parent) &&
!ExhaustiveDepsUtils.isQueryClientReference(reference)
!ExhaustiveDepsUtils.isInstanceOfKind(reference.identifier.parent)
)
},
isInstanceOfKind(node: TSESTree.Node) {
Expand All @@ -39,14 +38,4 @@ export const ExhaustiveDepsUtils = {
node.operator === 'instanceof'
)
},
isQueryClientReference(reference: TSESLint.Scope.Reference) {
const declarator = reference.resolved?.defs[0]?.node

return (
declarator?.type === AST_NODE_TYPES.VariableDeclarator &&
declarator.init?.type === AST_NODE_TYPES.CallExpression &&
declarator.init.callee.type === AST_NODE_TYPES.Identifier &&
declarator.init.callee.name === 'useQueryClient'
)
},
}
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ describe('InfiniteQueryBehavior', () => {

expect(queryFnSpy).toHaveBeenNthCalledWith(1, {
queryKey: key,
client: queryClient,
pageParam: 1,
meta: undefined,
direction: 'forward',
Expand All @@ -101,6 +102,7 @@ describe('InfiniteQueryBehavior', () => {

expect(queryFnSpy).toHaveBeenNthCalledWith(1, {
queryKey: key,
client: queryClient,
pageParam: 2,
direction: 'forward',
meta: undefined,
Expand All @@ -119,6 +121,7 @@ describe('InfiniteQueryBehavior', () => {

expect(queryFnSpy).toHaveBeenNthCalledWith(1, {
queryKey: key,
client: queryClient,
pageParam: 0,
direction: 'backward',
meta: undefined,
Expand All @@ -138,6 +141,7 @@ describe('InfiniteQueryBehavior', () => {

expect(queryFnSpy).toHaveBeenNthCalledWith(1, {
queryKey: key,
client: queryClient,
pageParam: -1,
meta: undefined,
direction: 'backward',
Expand All @@ -156,6 +160,7 @@ describe('InfiniteQueryBehavior', () => {

expect(queryFnSpy).toHaveBeenNthCalledWith(1, {
queryKey: key,
client: queryClient,
pageParam: 1,
meta: undefined,
direction: 'forward',
Expand All @@ -177,6 +182,7 @@ describe('InfiniteQueryBehavior', () => {

expect(queryFnSpy).toHaveBeenNthCalledWith(1, {
queryKey: key,
client: queryClient,
pageParam: 0,
meta: undefined,
direction: 'forward',
Expand All @@ -185,6 +191,7 @@ describe('InfiniteQueryBehavior', () => {

expect(queryFnSpy).toHaveBeenNthCalledWith(2, {
queryKey: key,
client: queryClient,
pageParam: 1,
meta: undefined,
direction: 'forward',
Expand Down Expand Up @@ -237,6 +244,7 @@ describe('InfiniteQueryBehavior', () => {

expect(queryFnSpy).toHaveBeenNthCalledWith(1, {
queryKey: key,
client: queryClient,
pageParam: 1,
meta: undefined,
direction: 'forward',
Expand Down Expand Up @@ -293,6 +301,7 @@ describe('InfiniteQueryBehavior', () => {

expect(queryFnSpy).toHaveBeenNthCalledWith(1, {
queryKey: key,
client: queryClient,
pageParam: 2,
meta: undefined,
direction: 'forward',
Expand Down
1 change: 1 addition & 0 deletions packages/query-core/src/__tests__/query.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@ describe('query', () => {
expect(args.pageParam).toBeUndefined()
expect(args.queryKey).toEqual(key)
expect(args.signal).toBeInstanceOf(AbortSignal)
expect(args.client).toEqual(queryClient)
})

test('should continue if cancellation is not supported and signal is not consumed', async () => {
Expand Down
1 change: 1 addition & 0 deletions packages/query-core/src/__tests__/queryClient.test-d.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ describe('defaultOptions', () => {
queries: {
queryFn: (context) => {
expectTypeOf(context).toEqualTypeOf<{
client: QueryClient
queryKey: QueryKey
meta: Record<string, unknown> | undefined
signal: AbortSignal
Expand Down
2 changes: 2 additions & 0 deletions packages/query-core/src/infiniteQueryBehavior.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export function infiniteQueryBehavior<TQueryFnData, TError, TData, TPageParam>(
QueryFunctionContext<QueryKey, unknown>,
'signal'
> = {
client: context.client,
queryKey: context.queryKey,
pageParam: param,
direction: previous ? 'backward' : 'forward',
Expand Down Expand Up @@ -114,6 +115,7 @@ export function infiniteQueryBehavior<TQueryFnData, TError, TData, TPageParam>(
return context.options.persister?.(
fetchFn as any,
{
client: context.client,
queryKey: context.queryKey,
meta: context.options.meta,
signal: context.signal,
Expand Down
12 changes: 9 additions & 3 deletions packages/query-core/src/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import {
import { notifyManager } from './notifyManager'
import { canFetch, createRetryer, isCancelledError } from './retryer'
import { Removable } from './removable'
import type { QueryCache } from './queryCache'
import type { QueryClient } from './queryClient'
import type {
CancelOptions,
DefaultError,
Expand All @@ -23,7 +25,6 @@ import type {
QueryStatus,
SetDataOptions,
} from './types'
import type { QueryCache } from './queryCache'
import type { QueryObserver } from './queryObserver'
import type { Retryer } from './retryer'

Expand All @@ -35,7 +36,7 @@ interface QueryConfig<
TData,
TQueryKey extends QueryKey = QueryKey,
> {
cache: QueryCache
client: QueryClient
queryKey: TQueryKey
queryHash: string
options?: QueryOptions<TQueryFnData, TError, TData, TQueryKey>
Expand Down Expand Up @@ -68,6 +69,7 @@ export interface FetchContext<
fetchOptions?: FetchOptions
signal: AbortSignal
options: QueryOptions<TQueryFnData, TError, TData, any>
client: QueryClient
queryKey: TQueryKey
state: QueryState<TData, TError>
}
Expand Down Expand Up @@ -167,6 +169,7 @@ export class Query<
#initialState: QueryState<TData, TError>
#revertState?: QueryState<TData, TError>
#cache: QueryCache
#client: QueryClient
#retryer?: Retryer<TData>
observers: Array<QueryObserver<any, any, any, any, any>>
#defaultOptions?: QueryOptions<TQueryFnData, TError, TData, TQueryKey>
Expand All @@ -179,7 +182,8 @@ export class Query<
this.#defaultOptions = config.defaultOptions
this.setOptions(config.options)
this.observers = []
this.#cache = config.cache
this.#client = config.client
this.#cache = this.#client.getQueryCache()
this.queryKey = config.queryKey
this.queryHash = config.queryHash
this.#initialState = getDefaultState(this.options)
Expand Down Expand Up @@ -411,6 +415,7 @@ export class Query<
QueryFunctionContext<TQueryKey>,
'signal'
> = {
client: this.#client,
queryKey: this.queryKey,
meta: this.meta,
}
Expand All @@ -437,6 +442,7 @@ export class Query<
fetchOptions,
options: this.options,
queryKey: this.queryKey,
client: this.#client,
state: this.state,
fetchFn,
}
Expand Down
2 changes: 1 addition & 1 deletion packages/query-core/src/queryCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ export class QueryCache extends Subscribable<QueryCacheListener> {

if (!query) {
query = new Query({
cache: this,
client,
queryKey,
queryHash,
options: client.defaultQueryOptions(options),
Expand Down
3 changes: 3 additions & 0 deletions packages/query-core/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/* istanbul ignore file */

import type { QueryClient } from './queryClient'
import type { DehydrateOptions, HydrateOptions } from './hydration'
import type { MutationState } from './mutation'
import type { FetchDirection, Query, QueryBehavior } from './query'
Expand Down Expand Up @@ -116,6 +117,7 @@ export type QueryFunctionContext<
TPageParam = never,
> = [TPageParam] extends [never]
? {
client: QueryClient
queryKey: TQueryKey
signal: AbortSignal
meta: QueryMeta | undefined
Expand All @@ -127,6 +129,7 @@ export type QueryFunctionContext<
direction?: unknown
}
: {
client: QueryClient
queryKey: TQueryKey
signal: AbortSignal
pageParam: TPageParam
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { describe, expect, test, vi } from 'vitest'
import { Query, QueryCache, hashKey } from '@tanstack/query-core'
import { Query, QueryClient, hashKey } from '@tanstack/query-core'
import {
PERSISTER_KEY_PREFIX,
experimental_createPersister,
} from '../createPersister'
import { sleep } from './utils'
import type { QueryFunctionContext, QueryKey } from '@tanstack/query-core'
import type { StoragePersisterOptions } from '../createPersister'
import type { QueryKey } from '@tanstack/query-core'

function getFreshStorage() {
const storage = new Map()
Expand All @@ -25,12 +25,14 @@ function setupPersister(
queryKey: QueryKey,
persisterOptions: StoragePersisterOptions,
) {
const client = new QueryClient()
const context = {
meta: { foo: 'bar' },
client,
queryKey,
// @ts-expect-error
signal: undefined as AbortSignal,
}
} satisfies QueryFunctionContext
const queryHash = hashKey(queryKey)
const storageKey = `${PERSISTER_KEY_PREFIX}-${queryHash}`

Expand All @@ -39,7 +41,7 @@ function setupPersister(
const persisterFn = experimental_createPersister(persisterOptions)

const query = new Query({
cache: new QueryCache(),
client,
queryHash,
queryKey,
})
Expand Down

0 comments on commit f86eb7e

Please sign in to comment.