Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/query client on context #8599

Merged
merged 12 commits into from
Jan 31, 2025
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',
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: we don’t need an exception for closing over the queryClient anymore if we inject it into the queryFn. @Newbie012 would you agree?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess so. I never had to deal with more than one query client in a single app. Is it a valid use case to reference a foreign query client inside queryFn?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nope, this is something the linter should definitely catch!

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be nice to get a getter for QueryClient from the instance of Query.
I missed that before for experimental persister.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you probably don’t need that now that it’s passed to the context, which the persister also has access to.

#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,
TkDodo marked this conversation as resolved.
Show resolved Hide resolved
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