A lightweight, type-safe, and scalable way to manage query keys for @tanstack/react-query
.
Designed to eliminate query key collisions, improve discoverability, and make key composition effortless.
Install via your preferred package manager:
# pnpm pnpm add react-query-key-manager # yarn yarn add react-query-key-manager # npm npm install react-query-key-manager
When working on medium-to-large projects with React Query, query keys quickly become a mess:
Magic strings everywhere — prone to typos and silent bugs.
Key collisions — multiple developers accidentally using the same key for different data.
Poor discoverability — no clear place to see all keys at once.
Inconsistent arguments — no type safety for parameters passed to keys.
These issues often surface late — during debugging or in production — instead of being caught at compile-time.
Use a namespaced key builder that:
-
Centralizes key definitions in a single source of truth.
-
Enforces type safety for key arguments.
-
Prevents duplicates both at compile time and at runtime (in dev).
-
Supports nested namespaces for large-scale projects.
-
Maintains zero runtime cost — types disappear after compilation.
This approach is lightweight enough to copy-paste directly into your project but also available via npm/yarn/pnpm.
queryKeys.ts
import { QueryKeyManager } from "react-query-key-manager"; export const userKeys = QueryKeyManager.create("user", { profile: (userId: string) => ["user", "profile", userId], settings: (userId: string) => ["user", "settings", userId], }); export const postKeys = QueryKeyManager.create("post", { list: (filters: { category: string }) => ["posts", filters], detail: (postId: string) => ["post", "detail", postId], }); // Nested namespaces supported export const adminKeys = QueryKeyManager.create("admin", { users: { list: (page: number) => ["admin", "users", "list", page], }, }); // Debugging — list all registered keys export const allQueryKeys = QueryKeyManager.getQueryKeys();
UserProfile.tsx
import { useQuery } from "@tanstack/react-query"; import { userKeys } from "./queryKeys"; function UserProfile({ userId }: { userId: string }) { const { data, isLoading } = useQuery({ queryKey: userKeys.profile(userId), queryFn: () => fetchUserProfile(userId), }); // ... }
export const extendedPostKeys = QueryKeyManager.create("post.extended", { withAuthor: (postId: string, authorId: string) => [ ...postKeys.detail(postId)(), "author", ...userKeys.profile(authorId)(), ], });
export const dashboardKeys = QueryKeyManager.create("dashboard", { summary: (userId: string) => [ "dashboard", "summary", ...userKeys.profile(userId)(), ...postKeys.list({ category: "featured" })(), ], });
- Duplicate Key Prevention
-
Compile-time: TypeScript errors if you try to redeclare a key name.
-
Runtime (dev): Throws if duplicate keys are detected.
- Full Type Inference
-
Function arguments are strictly typed.
-
Nested namespaces preserve their type signatures.
- Performance
-
Zero-cost abstractions — all type checks vanish after compilation.
-
Minimal object structure for fast inference.
For migrating legacy keys safely:
import { migrateLegacyKeys } from "react-query-key-manager"; import { userKeys } from "./queryKeys"; const legacyUserKey = migrateLegacyKeys("oldUserKey", (userId: string) => userKeys.profile(userId)() );
-
Zero Runtime Overhead — Pure TypeScript types.
-
Instant Editor Feedback — Type errors as you type.
-
Scalable Organization — Namespaced, nested keys.
-
Collision Protection — Compile + runtime safety.
-
Discoverability — getQueryKeys() shows all keys.
You can:
-
Install: pnpm add react-query-key-manager
-
Or copy-paste the implementation into your project directly from
src/index.ts