SuperAPI AI Directory

tanstack-query-intent-core-design-query-keys-and-options

Design & creation

View repository

Updated 10-16 00:02

About this skill

Setup

import { queryOptions } from '@tanstack/react-query'

export function todoOptions(todoId: string) {
  return queryOptions({
    queryKey: ['todo', todoId],
    queryFn: async () => ({ id: todoId, title: 'Ship skills' }),
    staleTime: 60_000,
  })
}

Core Patterns

Put every query variable in the key

import { queryOptions } from '@tanstack/react-query'

export function projectsOptions(teamId: string, page: number) {
  return queryOptions({
    queryKey: ['projects', teamId, page],
    queryFn: async () => ({ teamId, page, items: [] as Array<{ id: string }> }),
  })
}

Share one options factory

import { QueryClient, useQuery, queryOptions } from '@tanstack/react-query'

export const queryClient = new QueryClient()

export function userOptions(userId: string) {
  return queryOptions({
    queryKey: ['user', userId],
    queryFn: async () => ({ id: userId, name: 'Tanner' }),
  })
}

export function useUser(userId: string) {
  return useQuery(userOptions(userId))
}

export function preloadUser(userId: string) {
  return queryClient.ensureQueryData(userOptions(userId))
}

Use skipToken for typesafe absence

import { skipToken, useQuery } from '@tanstack/react-query'

export function useMaybeTodo(todoId: string | undefined) {
  return useQuery({
    queryKey: ['todo', todoId],
    queryFn: todoId ? async () => ({ id: todoId }) : skipToken,
  })
}

Common Mistakes

CRITICAL Missing variable in key

Wrong:

import { useQuery } from '@tanstack/react-query'

export function useProject(teamId: string) {
  return useQuery({
    queryKey: ['project'],
    queryFn: async () => ({ teamId }),
  })
}

Correct:

import { useQuery } from '@tanstack/react-query'

export function useProject(teamId: string) {
  return useQuery({
    queryKey: ['project', teamId],
    queryFn: async () => ({ teamId }),
  })
}

Query keys define cache identity; missing variables merge distinct data.

Source: TanStack/query:docs/framework/react/guides/query-keys.md

HIGH Key and queryFn drift

Wrong:

import { useQuery } from '@tanstack/react-query'

export function useTodo(todoId: string) {
  return useQuery({ queryKey: ['todo'], queryFn: async () => ({ id: todoId }) })
}

Correct:

import { queryOptions, useQuery } from '@tanstack/react-query'

function todoOptions(todoId: string) {
  return queryOptions({
    queryKey: ['todo', todoId],
    queryFn: async () => ({ id: todoId }),
  })
}

export function useTodo(todoId: string) {
  return useQuery(todoOptions(todoId))
}

Options factories keep identity, fetch behavior, and inference together across hooks and prefetches.

Source: TanStack/query:docs/eslint/prefer-query-options.md

HIGH skipToken inside suspense query

Wrong:

import { skipToken, useSuspenseQuery } from '@tanstack/react-query'

export function useTodo(todoId: string | undefined) {
  return useSuspenseQuery({
    queryKey: ['todo', todoId],
    queryFn: todoId ? async () => ({ id: todoId }) : skipToken,
  })
}

Correct:

import { skipToken, useQuery } from '@tanstack/react-query'

export function useTodo(todoId: string | undefined) {
  return useQuery({
    queryKey: ['todo', todoId],
    queryFn: todoId ? async () => ({ id: todoId }) : skipToken,
  })
}

Suspense queries require a guaranteed query function and cannot be conditionally disabled.

Source: TanStack/query:docs/framework/react/guides/suspense.md

See also: compositions/enforce-query-best-practices-with-eslint for rules that enforce key and options mistakes.