tanstack-query-keys
v0.1.2
Published
Type-safe query key factory & store for TanStack Query v5 / React Query — hierarchical query keys, queryOptions & infiniteQueryOptions, contextQueries, mutation keys, and scoped cache invalidation.
Maintainers
Readme
tanstack-query-keys
Type-safe query key factory & store for TanStack Query v5 (React Query). Organize your query keys in a typed hierarchy, emit native
queryOptions/infiniteQueryOptions, and invalidate the cache by scope with_defhandles — everything you need for clean cache management in React Query v5.
A modern, v5-first take on the query-key-factory pattern. TanStack Query v5
already solves colocation and typing with queryOptions(); what it doesn't
give you is an organized key hierarchy with scoped invalidation handles.
This library adds exactly that — with full DataTag typing and zero runtime deps.
queryClient.invalidateQueries({ queryKey: queries.users._def }); // everything user-related
queryClient.invalidateQueries({ queryKey: queries.users.detail._def }); // all user detailsWhy
- v5-native types. Keys are branded with
DataTag, soqueryClient.getQueryData(queries.users.detail(id).queryKey)is fully typed — nounknown. - Colocate any option.
staleTime,select,enabled,placeholderData,getNextPageParam, … — anythinguseQuery/useInfiniteQueryaccepts. - Infinite queries work. Pass
initialPageParam+getNextPageParamright in the definition. - Scoped
_defhandles at every level for partial-key invalidation. - Contextual queries (
contextQueries→_ctx) for related sub-queries. - Mutation keys (
createMutationKeys) with the same ergonomics. - Inference helpers —
inferQueryKeyStore,inferQueryKeys,TypedUseQueryOptions. - Zero runtime deps. Just plain key/options objects.
Install
Install the package together with its peer dependency, TanStack Query v5:
# npm
npm install tanstack-query-keys @tanstack/react-query
# pnpm
pnpm add tanstack-query-keys @tanstack/react-query
# yarn
yarn add tanstack-query-keys @tanstack/react-query
# bun
bun add tanstack-query-keys @tanstack/react-queryRequirements:
@tanstack/react-queryv5+ (v4 is not supported) · TypeScript 5.0+ · Node 20+. Zero runtime dependencies.
Usage
One store
import { createQueryStore } from 'tanstack-query-keys';
export const queries = createQueryStore({
users: {
all: null,
list: {
queryKey: null,
queryFn: () => api.getUsers(),
staleTime: 60_000,
},
detail: (id: string) => ({
queryKey: [id],
queryFn: () => api.getUser(id),
}),
},
todos: {
feed: (filter: string) => ({
queryKey: [filter],
queryFn: (ctx: { pageParam: number }) => api.getTodos(ctx.pageParam),
initialPageParam: 0,
getNextPageParam: (last) => last.nextCursor,
}),
},
});No
as constneeded. Keys, options, andgetQueryDatatyping all work as shown. Addas constto a key array only if you want an exact tuple type (['users','detail', string]) instead of the default spread (['users','detail', ...string[]]) — e.g. for positional key destructuring. Functionally they're identical.
Consume
import { useQuery, useInfiniteQuery } from '@tanstack/react-query';
useQuery(queries.users.detail(id)); // spread-free — it's already options
useInfiniteQuery(queries.todos.feed(filter)); // infinite options included
// typed cache access, no casts
const user = queryClient.getQueryData(queries.users.detail(id).queryKey); // User | undefinedFeature-colocated (merge)
// queries/users.ts
export const users = createQueryKeys('users', { all: null, /* … */ });
// queries/todos.ts
export const todos = createQueryKeys('todos', { /* … */ });
// queries/index.ts
export const queries = mergeQueryKeys(users, todos);Contextual queries (_ctx)
Group sub-queries that depend on a parent context (e.g. a user's likes):
export const users = createQueryKeys('users', {
detail: (id: string) => ({
queryKey: [id],
queryFn: () => api.getUser(id),
contextQueries: {
likes: { queryKey: null, queryFn: () => api.getUserLikes(id) },
comments: (page: number) => ({
queryKey: [page],
queryFn: () => api.getUserComments(id, page),
}),
},
}),
});
users.detail('1')._ctx.likes.queryKey; // ['users','detail','1','likes']
users.detail('1')._ctx.comments(2).queryKey; // ['users','detail','1','comments',2]
useQuery(users.detail('1')._ctx.likes); // typed, ready to useMutation keys
import { createMutationKeys } from 'tanstack-query-keys';
export const userMutations = createMutationKeys('users', {
update: (id: string) => ({
mutationKey: [id],
mutationFn: (patch: Partial<User>) => api.updateUser(id, patch),
}),
delete: null,
});
useMutation(userMutations.update(id));
userMutations.update._def; // ['users','update'] — handle for all update mutationsTyped inference helpers
import type { inferQueryKeyStore, TypedUseQueryOptions } from 'tanstack-query-keys';
export type QueryKeys = inferQueryKeyStore<typeof queries>;
// Type a custom hook's options, inferring data from the factory's queryFn:
type DetailOptions = TypedUseQueryOptions<typeof queries.users.detail>;
// transform with `select`? pass the result type as the 2nd arg:
type Name = TypedUseQueryOptions<typeof queries.users.detail, string>;Key shape
createQueryStore({ users: { detail: (id) => ({ queryKey: [id] }) } })
queries.users._def → ['users']
queries.users.detail._def → ['users', 'detail']
queries.users.detail(7).queryKey→ ['users', 'detail', 7]Credit
The hierarchical-store + _def ergonomics are inspired by
@lukemorales/query-key-factory.
This is an independent, v5-first reimplementation built around native
queryOptions/DataTag typing.
License
MIT
