query-key-factory icon indicating copy to clipboard operation
query-key-factory copied to clipboard

A library for creating typesafe standardized query keys, useful for cache management in @tanstack/query

tRPC

Query Key Factory

Latest build Latest published version Bundlephobia Tree shaking available Types included License Number of downloads GitHub Stars

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)