diff --git a/packages/react-query/src/createTRPCReact.tsx b/packages/react-query/src/createTRPCReact.tsx index b3416d8f051..d422c952b52 100644 --- a/packages/react-query/src/createTRPCReact.tsx +++ b/packages/react-query/src/createTRPCReact.tsx @@ -15,6 +15,7 @@ import { inferTransformedSubscriptionOutput, } from '@trpc/server/shared'; import { useMemo } from 'react'; +import { QueryKey, QueryType } from './internals/getArrayQueryKey'; import { TRPCUseQueries } from './internals/useQueries'; import { CreateReactUtilsProxy, @@ -48,6 +49,15 @@ export type DecorateProcedure< TPath extends string, > = TProcedure extends AnyQueryProcedure ? { + /** + * Method to extract the query key for a procedure + * @param type - defaults to `any` + * @link https://trpc.io/docs/useContext#-the-function-i-want-isnt-here + */ + getQueryKey: ( + input: inferProcedureInput, + type?: QueryType, + ) => QueryKey; useQuery: < TQueryFnData = inferTransformedProcedureOutput, TData = inferTransformedProcedureOutput, @@ -164,7 +174,9 @@ export type DecoratedProcedureRecord< TPath extends string = '', > = { [TKey in keyof TProcedures]: TProcedures[TKey] extends AnyRouter - ? DecoratedProcedureRecord< + ? { + getQueryKey: () => QueryKey; + } & DecoratedProcedureRecord< TProcedures[TKey]['_def']['record'], TFlags, `${TPath}${TKey & string}.` diff --git a/packages/react-query/src/internals/context.tsx b/packages/react-query/src/internals/context.tsx index fbe0b0e7e50..3474dfab68c 100644 --- a/packages/react-query/src/internals/context.tsx +++ b/packages/react-query/src/internals/context.tsx @@ -160,13 +160,6 @@ export interface TRPCContextState< filters?: InvalidateQueryFilters, options?: InvalidateOptions, ): Promise; - /** - * @link https://react-query.tanstack.com/guides/query-invalidation - */ - invalidateQueries( - filters?: InvalidateQueryFilters, - options?: InvalidateOptions, - ): Promise; /** * @link https://react-query.tanstack.com/reference/QueryClient#queryclientresetqueries diff --git a/packages/react-query/src/internals/getArrayQueryKey.test.ts b/packages/react-query/src/internals/getArrayQueryKey.test.ts index 98c1496d107..432f1854693 100644 --- a/packages/react-query/src/internals/getArrayQueryKey.test.ts +++ b/packages/react-query/src/internals/getArrayQueryKey.test.ts @@ -1,6 +1,17 @@ import { getArrayQueryKey } from './getArrayQueryKey'; test('getArrayQueryKey', () => { + // empty path should not nest an extra array + expect(getArrayQueryKey('', 'any')).toMatchInlineSnapshot(`Array []`); + + // should not nest an empty object + expect(getArrayQueryKey('foo', 'any')).toMatchInlineSnapshot(` + Array [ + Array [ + "foo", + ], + ] + `); expect(getArrayQueryKey('foo', 'query')).toMatchInlineSnapshot(` Array [ Array [ diff --git a/packages/react-query/src/internals/getArrayQueryKey.ts b/packages/react-query/src/internals/getArrayQueryKey.ts index 850227fec3c..ab0bf8804bd 100644 --- a/packages/react-query/src/internals/getArrayQueryKey.ts +++ b/packages/react-query/src/internals/getArrayQueryKey.ts @@ -1,16 +1,21 @@ export type QueryType = 'query' | 'infinite' | 'any'; +export type QueryKey = [ + string[], + { input?: unknown; type?: Exclude }?, +]; + /** * To allow easy interactions with groups of related queries, such as * invalidating all queries of a router, we use an array as the path when * storing in tanstack query. This function converts from the `.` separated * path passed around internally by both the legacy and proxy implementation. * https://github.com/trpc/trpc/issues/2611 - */ + **/ export function getArrayQueryKey( queryKey: string | [string] | [string, ...unknown[]] | unknown[], type: QueryType, -): [string[], { input?: unknown; type?: Exclude }] { +): QueryKey { const queryKeyArrayed = Array.isArray(queryKey) ? queryKey : [queryKey]; const [path, input] = queryKeyArrayed; @@ -20,6 +25,10 @@ export function getArrayQueryKey( // Construct a query key that is easy to destructure and flexible for // partial selecting etc. // https://github.com/trpc/trpc/issues/3128 + if (!input && (!type || type === 'any')) + // for `utils.invalidate()` to match all queries (including vanilla react-query) + // we don't want nested array if path is empty, i.e. `[]` instead of `[[]]` + return arrayPath.length ? [arrayPath] : ([] as unknown as QueryKey); return [ arrayPath, { diff --git a/packages/react-query/src/internals/getQueryKey.ts b/packages/react-query/src/internals/getQueryKey.ts index 51a8da89e09..87fda4b5bfe 100644 --- a/packages/react-query/src/internals/getQueryKey.ts +++ b/packages/react-query/src/internals/getQueryKey.ts @@ -6,5 +6,6 @@ export function getQueryKey( path: string, input: unknown, ): [string] | [string, unknown] { - return input === undefined ? [path] : [path, input]; + if (path.length) return input === undefined ? [path] : [path, input]; + return [] as unknown as [string]; } diff --git a/packages/react-query/src/shared/proxy/decorationProxy.ts b/packages/react-query/src/shared/proxy/decorationProxy.ts index 2cdec3eca0c..106b67dc913 100644 --- a/packages/react-query/src/shared/proxy/decorationProxy.ts +++ b/packages/react-query/src/shared/proxy/decorationProxy.ts @@ -1,5 +1,6 @@ import { AnyRouter } from '@trpc/server'; import { createRecursiveProxy } from '@trpc/server/shared'; +import { getArrayQueryKey } from '../../internals/getArrayQueryKey'; import { getQueryKey } from '../../internals/getQueryKey'; import { CreateReactQueryHooks } from '../hooks/createHooksInternal'; @@ -28,6 +29,12 @@ export function createReactProxyDecoration< const [input, ...rest] = args; const queryKey = getQueryKey(path, input); + + // Expose queryKey helper + if (lastArg === 'getQueryKey') { + return getArrayQueryKey(queryKey, (rest[0] as any) ?? 'any'); + } + if (lastArg.startsWith('useSuspense')) { const opts = rest[0] || {}; const fn = diff --git a/packages/react-query/src/shared/proxy/utilsProxy.ts b/packages/react-query/src/shared/proxy/utilsProxy.ts index 686f09be097..3b554752282 100644 --- a/packages/react-query/src/shared/proxy/utilsProxy.ts +++ b/packages/react-query/src/shared/proxy/utilsProxy.ts @@ -171,6 +171,7 @@ type DecorateRouter = { * @link https://react-query.tanstack.com/guides/query-invalidation */ invalidate( + input?: undefined, filters?: InvalidateQueryFilters, options?: InvalidateOptions, ): Promise; diff --git a/packages/tests/server/interop/react/invalidateQueries.test.tsx b/packages/tests/server/interop/react/invalidateQueries.test.tsx index e2937687184..80cca9838fe 100644 --- a/packages/tests/server/interop/react/invalidateQueries.test.tsx +++ b/packages/tests/server/interop/react/invalidateQueries.test.tsx @@ -192,12 +192,15 @@ describe('invalidateQueries()', () => {