Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/lukemorales/query-key-factory
A library for creating typesafe standardized query keys, useful for cache management in @tanstack/query
https://github.com/lukemorales/query-key-factory
cache query query-keys react react-query solid-query svelte-query tanstack tanstack-react-query vue-query
Last synced: 4 days ago
JSON representation
A library for creating typesafe standardized query keys, useful for cache management in @tanstack/query
- Host: GitHub
- URL: https://github.com/lukemorales/query-key-factory
- Owner: lukemorales
- License: mit
- Created: 2022-08-21T23:18:48.000Z (over 2 years ago)
- Default Branch: main
- Last Pushed: 2024-12-29T23:35:37.000Z (about 1 month ago)
- Last Synced: 2025-01-31T20:46:54.102Z (8 days ago)
- Topics: cache, query, query-keys, react, react-query, solid-query, svelte-query, tanstack, tanstack-react-query, vue-query
- Language: TypeScript
- Homepage: https://www.npmjs.com/package/@lukemorales/query-key-factory
- Size: 1020 KB
- Stars: 1,331
- Watchers: 8
- Forks: 33
- Open Issues: 17
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE.md
Awesome Lists containing this project
README
Query Key Factory
Typesafe query key management for @tanstack/query with auto-completion features.
Focus on writing and invalidating queries without the hassle of remembering
how you've set up a key for a specific query! This lib will take care of the rest.## 📦 Install
Query Key Factory is available as a package on NPM, install with your favorite package manager:```dircolors
npm install @lukemorales/query-key-factory
```## ⚡ Quick start
Start by defining the query keys for the features of your app:### Declare your store in a single file
```ts
import { createQueryKeyStore } from "@lukemorales/query-key-factory";// if you prefer to declare everything in one file
export const queries = createQueryKeyStore({
users: {
all: null,
detail: (userId: string) => ({
queryKey: [userId],
queryFn: () => api.getUser(userId),
}),
},
todos: {
detail: (todoId: string) => [todoId],
list: (filters: TodoFilters) => ({
queryKey: [{ filters }],
queryFn: (ctx) => api.getTodos({ filters, page: ctx.pageParam }),
contextQueries: {
search: (query: string, limit = 15) => ({
queryKey: [query, limit],
queryFn: (ctx) => api.getSearchTodos({
page: ctx.pageParam,
filters,
limit,
query,
}),
}),
},
}),
},
});
```### Fine-grained declaration colocated by features
```ts
import { createQueryKeys, mergeQueryKeys } from "@lukemorales/query-key-factory";// queries/users.ts
export const users = createQueryKeys('users', {
all: null,
detail: (userId: string) => ({
queryKey: [userId],
queryFn: () => api.getUser(userId),
}),
});// queries/todos.ts
export const todos = createQueryKeys('todos', {
detail: (todoId: string) => [todoId],
list: (filters: TodoFilters) => ({
queryKey: [{ filters }],
queryFn: (ctx) => api.getTodos({ filters, page: ctx.pageParam }),
contextQueries: {
search: (query: string, limit = 15) => ({
queryKey: [query, limit],
queryFn: (ctx) => api.getSearchTodos({
page: ctx.pageParam,
filters,
limit,
query,
}),
}),
},
}),
});// queries/index.ts
export const queries = mergeQueryKeys(users, todos);
```Use throughout your codebase as the single source for writing the query keys, or even the complete queries for your cache management:
```ts
import { queries } from '../queries';export function useUsers() {
return useQuery({
...queries.users.all,
queryFn: () => api.getUsers(),
});
};export function useUserDetail(id: string) {
return useQuery(queries.users.detail(id));
};
``````ts
import { queries } from '../queries';export function useTodos(filters: TodoFilters) {
return useQuery(queries.todos.list(filters));
};export function useSearchTodos(filters: TodoFilters, query: string, limit = 15) {
return useQuery({
...queries.todos.list(filters)._ctx.search(query, limit),
enabled: Boolean(query),
});
};export function useUpdateTodo() {
const queryClient = useQueryClient();return useMutation(updateTodo, {
onSuccess(newTodo) {
queryClient.setQueryData(queries.todos.detail(newTodo.id).queryKey, newTodo);// invalidate all the list queries
queryClient.invalidateQueries({
queryKey: queries.todos.list._def,
refetchActive: false,
});
},
});
};
```## 📝 Features
### Standardized keys
All keys generated follow the @tanstack/query convention of being an [array at top level](https://tanstack.com/query/v4/docs/framework/react/guides/query-keys), including [keys with serializable objects](https://tanstack.com/query/v4/docs/framework/react/guides/query-keys#array-keys-with-variables):```ts
export const todos = createQueryKeys('todos', {
detail: (todoId: string) => [todoId],
list: (filters: TodoFilters) => ({
queryKey: [{ filters }],
}),
});// => createQueryKeys output:
// {
// _def: ['todos'],
// detail: (todoId: string) => {
// queryKey: ['todos', 'detail', todoId],
// },
// list: (filters: TodoFilters) => {
// queryKey: ['todos', 'list', { filters }],
// },
// }
````queryKey` can be optional when there's no need for a dynamic query:
```ts
export const users = createQueryKeys('users', {
list: {
queryKey: null,
queryFn: () => api.getUsers(),
}
});
```### Generate the query options you need to run `useQuery`
Declare your `queryKey` and your `queryFn` together, and have easy access to everything you need to run a query:```ts
export const users = createQueryKeys('users', {
detail: (userId: string) => ({
queryKey: [userId],
queryFn: () => api.getUser(userId),
}),
});// => createQueryKeys output:
// {
// _def: ['users'],
// detail: (userId: string) => {
// queryKey: ['users', 'detail', userId],
// queryFn: (ctx: QueryFunctionContext) => api.getUser(userId),
// },
// }export function useUserDetail(id: string) {
return useQuery(users.detail(id));
};
```### Generate contextual queries
Declare queries that are dependent or related to a parent context (e.g.: all likes from a user):```ts
export const users = createQueryKeys('users', {
detail: (userId: string) => ({
queryKey: [userId],
queryFn: () => api.getUser(userId),
contextQueries: {
likes: {
queryKey: null,
queryFn: () => api.getUserLikes(userId),
},
},
}),
});// => createQueryKeys output:
// {
// _def: ['users'],
// detail: (userId: string) => {
// queryKey: ['users', 'detail', userId],
// queryFn: (ctx: QueryFunctionContext) => api.getUser(userId),
// _ctx: {
// likes: {
// queryKey: ['users', 'detail', userId, 'likes'],
// queryFn: (ctx: QueryFunctionContext) => api.getUserLikes(userId),
// },
// },
// },
// }export function useUserLikes(userId: string) {
return useQuery(users.detail(userId)._ctx.likes);
};
```### Access to serializable keys scope definition
Easy way to access the serializable key scope and invalidate all cache for that context:```ts
users.detail(userId).queryKey; // => ['users', 'detail', userId]
users.detail._def; // => ['users', 'detail']
```### Create a single point of access for all your query keys
#### Declare your query keys store in a single file
Just one place to edit and maintain your store:
```ts
export const queries = createQueryKeyStore({
users: {
all: null,
detail: (userId: string) => ({
queryKey: [userId],
queryFn: () => api.getUser(userId),
}),
},
todos: {
detail: (todoId: string) => [todoId],
list: (filters: TodoFilters) => ({
queryKey: [{ filters }],
queryFn: (ctx) => api.getTodos({ filters, page: ctx.pageParam }),
}),
},
});
```#### Declare your query keys by feature
Have fine-grained control over your features' keys and merge them into a single object to have access to all your query keys in your codebase:```ts
// queries/users.ts
export const users = createQueryKeys('users', {
all: null,
detail: (userId: string) => ({
queryKey: [userId],
queryFn: () => api.getUser(userId),
}),
});// queries/todos.ts
export const todos = createQueryKeys('todos', {
detail: (todoId: string) => [todoId],
list: (filters: TodoFilters) => ({
queryKey: [{ filters }],
queryFn: (ctx) => api.getTodos({ filters, page: ctx.pageParam }),
}),
});// queries/index.ts
export const queries = mergeQueryKeys(users, todos);
```### Type safety and smart autocomplete
Typescript is a first class citizen of Query Key Factory, providing easy of use and autocomplete for all query keys available and their outputs. Don't remember if a key is serializable or the shape of a key? Just let your IDE show you all information you need.#### Infer the type of the store's query keys
```ts
import { createQueryKeyStore, inferQueryKeyStore } from "@lukemorales/query-key-factory";export const queries = createQueryKeyStore({
/* ... */
});export type QueryKeys = inferQueryKeyStore;
```
```ts
// queries/index.ts
import { mergeQueryKeys, inferQueryKeyStore } from "@lukemorales/query-key-factory";import { users } from './users';
import { todos } from './todos';export const queries = mergeQueryKeys(users, todos);
export type QueryKeys = inferQueryKeyStore;
```#### Infer the type of a feature's query keys
```ts
import { createQueryKeys, inferQueryKeys } from "@lukemorales/query-key-factory";export const todos = createQueryKeys('todos', {
detail: (todoId: string) => [todoId],
list: (filters: TodoFilters) => ({
queryKey: [{ filters }],
queryFn: (ctx) => api.getTodos({ filters, page: ctx.pageParam }),
}),
});export type TodosKeys = inferQueryKeys;
```#### Type your QueryFunctionContext with ease
Get accurate types of your query keys passed to the `queryFn` context:```ts
import type { QueryKeys } from "../queries";
// import type { TodosKeys } from "../queries/todos";type TodosList = QueryKeys['todos']['list'];
// type TodosList = TodosKeys['list'];const fetchTodos = async (ctx: QueryFunctionContext) => {
const [, , { filters }] = ctx.queryKey;return api.getTodos({ filters, page: ctx.pageParam });
}export function useTodos(filters: TodoFilters) {
return useQuery({
...queries.todos.list(filters),
queryFn: fetchTodos,
});
};
```