00
:
00
:
00
:
00
Corso SEO AI - Usa SEOEMAIL al checkout per il 30% di sconto

Query Keys

Struttura delle Query Keys

Le query keys sono array che identificano univocamente una query nella cache. TanStack Query usa queste chiavi per il caching, la deduplicazione e l’invalidazione.

// Key semplice - lista di tutti i todos
useQuery({ queryKey: ['todos'], queryFn: fetchTodos })

// Key con parametro - singolo todo
useQuery({ queryKey: ['todos', todoId], queryFn: () => fetchTodo(todoId) })

// Key con filtri
useQuery({
  queryKey: ['todos', { status: 'active', page: 1 }],
  queryFn: () => fetchTodos({ status: 'active', page: 1 }),
})

Array Keys

Le chiavi sono sempre array, anche con un solo elemento. TanStack Query confronta le chiavi tramite deep equality:

// Queste sono la STESSA query (ordine dell'oggetto non conta)
useQuery({ queryKey: ['todos', { page: 1, status: 'active' }], ... })
useQuery({ queryKey: ['todos', { status: 'active', page: 1 }], ... })

// Queste sono query DIVERSE (ordine degli elementi dell'array conta)
useQuery({ queryKey: ['todos', 1], ... })
useQuery({ queryKey: [1, 'todos'], ... })

Oggetti nelle Keys

Gli oggetti all’interno delle chiavi sono confrontati per valore, non per riferimento:

interface TodoFilters {
  status: 'all' | 'active' | 'completed'
  sortBy: 'date' | 'priority'
}

function useTodos(filters: TodoFilters) {
  return useQuery({
    queryKey: ['todos', filters],
    queryFn: () => fetchTodos(filters),
  })
}

// Uso nel componente
function TodoList() {
  const [filters, setFilters] = useState<TodoFilters>({
    status: 'active',
    sortBy: 'date',
  })

  // La query cambia automaticamente quando cambiano i filtri
  const { data } = useTodos(filters)

  return (
    <div>
      <select onChange={(e) => setFilters(f => ({ ...f, status: e.target.value as any }))}>
        <option value="all">Tutti</option>
        <option value="active">Attivi</option>
        <option value="completed">Completati</option>
      </select>
      {data?.map(todo => <TodoItem key={todo.id} todo={todo} />)}
    </div>
  )
}

Query Key Factory

Per progetti di medie/grandi dimensioni, e’ consigliato creare una factory che centralizza tutte le chiavi:

// keys/todo.keys.ts
export const todoKeys = {
  all: ['todos'] as const,
  lists: () => [...todoKeys.all, 'list'] as const,
  list: (filters: TodoFilters) => [...todoKeys.lists(), filters] as const,
  details: () => [...todoKeys.all, 'detail'] as const,
  detail: (id: number) => [...todoKeys.details(), id] as const,
}

Uso della factory:

// Fetch lista con filtri
useQuery({
  queryKey: todoKeys.list({ status: 'active', sortBy: 'date' }),
  queryFn: () => fetchTodos({ status: 'active', sortBy: 'date' }),
})

// Fetch singolo todo
useQuery({
  queryKey: todoKeys.detail(5),
  queryFn: () => fetchTodo(5),
})

Factory per Entita’ Multiple

// keys/index.ts
export const userKeys = {
  all: ['users'] as const,
  lists: () => [...userKeys.all, 'list'] as const,
  list: (params: UserParams) => [...userKeys.lists(), params] as const,
  details: () => [...userKeys.all, 'detail'] as const,
  detail: (id: string) => [...userKeys.details(), id] as const,
  profile: (id: string) => [...userKeys.detail(id), 'profile'] as const,
}

export const postKeys = {
  all: ['posts'] as const,
  byUser: (userId: string) => [...postKeys.all, 'user', userId] as const,
  detail: (id: string) => [...postKeys.all, 'detail', id] as const,
  comments: (postId: string) => [...postKeys.detail(postId), 'comments'] as const,
}

Invalidazione Basata su Prefix

L’invalidazione funziona con matching parziale: se invalidi una chiave, tutte le query che iniziano con quella chiave vengono invalidate.

const queryClient = useQueryClient()

// Invalida TUTTE le query dei todos (lista, dettagli, filtri)
queryClient.invalidateQueries({ queryKey: todoKeys.all })
// Invalida: ['todos'], ['todos', 'list', ...], ['todos', 'detail', ...]

// Invalida solo le liste (non i dettagli)
queryClient.invalidateQueries({ queryKey: todoKeys.lists() })
// Invalida: ['todos', 'list'], ['todos', 'list', { status: 'active' }]

// Invalida un singolo dettaglio
queryClient.invalidateQueries({ queryKey: todoKeys.detail(5) })
// Invalida: ['todos', 'detail', 5]

Invalidazione con Filtri Avanzati

// Invalida solo le query attive (montate in un componente)
queryClient.invalidateQueries({
  queryKey: ['todos'],
  type: 'active',
})

// Invalida solo le query inattive
queryClient.invalidateQueries({
  queryKey: ['todos'],
  type: 'inactive',
})

// Invalida con un predicato custom
queryClient.invalidateQueries({
  predicate: (query) => {
    return query.queryKey[0] === 'todos' && query.state.dataUpdatedAt < Date.now() - 60000
  },
})

Convenzioni di Naming

Ecco le convenzioni consigliate per mantenere le chiavi organizzate:

// 1. Usa nomi di entita' al plurale come primo elemento
['users'], ['posts'], ['comments']

// 2. Aggiungi 'list' o 'detail' come secondo livello
['users', 'list'], ['users', 'detail', userId]

// 3. Aggiungi filtri come oggetto nell'ultimo elemento
['users', 'list', { role: 'admin', page: 2 }]

// 4. Per relazioni, usa chiavi annidate
['users', userId, 'posts']  // posts di un utente specifico

// 5. Centralizza tutto in una factory
export const keys = {
  users: userKeys,
  posts: postKeys,
  comments: commentKeys,
}