query-key-factory
query-key-factory copied to clipboard
A library for creating typesafe standardized query keys, useful for cache management in @tanstack/query
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:
npm install @lukemorales/query-key-factory
⚡ Quick start
Start by defining the query keys for the features of your app:
import { createQueryKeys } from "@lukemorales/query-key-factory"
export const usersKeys = createQueryKeys('users');
export const productsKeys = createQueryKeys('products', {
bestSelling: null,
search: (query: string, limit = 15) => ({ query, limit }),
byId: (productId: string) => ({ productId }),
});
Use throughout your codebase as the single source for writing the query keys for your cache management:
import { usersKeys, productsKeys } from '../query-keys';
const UserList: FC = () => {
const users = useQuery(usersKeys.default, fetchUsers);
return <div> {/* render product page */} </div>;
};
const ProductList: FC = () => {
const [search, setSeach] = useState('');
const [productsPerPage, setProductsPerPage] = useState(15);
const products = useQuery(productsKeys.search(search, productsPerPage), fetchProducts);
useEffect(() => {
if (search === '') {
// invalidate cache only for the search scope
queryClient.invalidateQueries(productKeys.search.toScope());
}
}, [search]);
return <div> {/* render product list */} </div>;
};
const Product: FC = () => {
const { productId } = useParams();
const product = useQuery(productsKeys.byId(productId), fetchProduct);
const onAddToCart = () => {
// invalidade cache for entire feature
queryClient.invalidateQueries(productsKeys.default);
}
return <div> {/* render product page */} </div>;
}
📝 Features
Typesafe and autocomplete
Typescript is a first class citizen of the Query Key Factory lib, 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 mouseover and your IDE will show you all information you need to know.
Standardized keys
All keys generated follow the @tanstack/query recommendation of being an array at top level, including keys with serializable objects:
const todosKeys = createQueryKeys('todos', {
done: null,
preview: true,
single: (id: string) => id,
});
// shape of createQueryKeys output
todosKeys = {
default: ['todos'],
done: ['todos', 'done'],
preview: ['todos', 'preview', true],
single: ('todo_id') => ['todos', 'single', 'todo_id'],
}
Access to serializable keys scoped form
Easy way to access the serializable key scope and invalidade all cache for that context:
const todosKeys = createQueryKeys('todos', {
single: (id: string) => id,
tag: (tagId: string) => ({ tagId }),
search: (query: string, limit: number) => [query, { limit }],
filter: ({ filter, status, limit }: FilterOptions) => [filter, status, limit],
});
todosKeys.single('todo_id');
// ['todos', 'single', 'todo_id']
todosKeys.tag('tag_homework');
// ['todos', 'tag', { tagId: 'tag_homework' }]
todosKeys.search('learn tanstack query', 15);
// ['todos', 'search', 'learn tanstack query', { limit: 15 }]
todosKeys.filter({ filter: 'not-owned-by-me', status: 'done', limit: 15 });
// ['todos', 'filter', 'not-owned-by-me', 'done', 15]
todosKeys.single.toScope(); // ['todos', 'single']
todosKeys.tag.toScope(); // ['todos', 'tag']
todosKeys.search.toScope(); // ['todos', 'search']
Type your QueryFunctionContext
Get types of your query keys passed to the queryFn
import { createQueryKeys, inferQueryKeys } from "@lukemorales/query-key-factory"
const todosKeys = createQueryKeys('todos', {
single: (id: string) => id,
tag: (tagId: string) => ({ tagId }),
search: (query: string, limit: number) => [query, { limit }],
filter: ({ filter, status, limit }: FilterOptions) => [filter, status, limit],
});
type TodoKeys = inferQueryKeys<typeof todosKeys>
const fetchTodosByTag = (context: QueryFunctionContext<TodoKeys['tag']>) => {
const queryKey = context.queryKey // readonly ['todos', 'tag', { tagId: string }]
// fetch todos...
}
useQuery(todosKeys.tag('tag_homework'), fetchTodosByTag)