Description
Describe the bug
I just started moving code over to @TkDodo's [Query Options API pattern]](https://tkdodo.eu/blog/the-query-options-api). I noticed something was not invalidating, and it's because I forgot to .queryKey
from my query options factory.
I would expect that invalidateQueries would validate that a queryKey needs to be an array. For some reason if queryKey: someValueThatIsOfTypeQueryOptions, it does not throw a type error. You can try out the example below.
Notice how this invalidateQueries is not throwing a type error here. This is problematic, because if you forget to .queryKey, the invalidation will not work.
// No error
queryClient.invalidateQueries({
// THIS DOES NOT SHOW A TYPE ERROR ON 5.66.9
queryKey: getTodosOptions,
});
// Should actually be
queryClient.invalidateQueries({
// THIS DOES NOT SHOW A TYPE ERROR ON 5.66.9
queryKey: getTodosOptions.queryKey,
});
'use client';
import { queryOptions, useQuery, useQueryClient } from '@tanstack/react-query';
import { useState } from 'react';
interface Todo {
id: number;
title: string;
completed: boolean;
}
// Mock API call
const fetchTodos = async (): Promise<Todo[]> => {
// Simulate API delay
await new Promise((resolve) => setTimeout(resolve, 1000));
// Add some randomness to demonstrate invalidation
const randomSuffix = Math.floor(Math.random() * 100);
return [
{ id: 1, title: `Learn React Query (${randomSuffix})`, completed: false },
{ id: 2, title: `Build a D&D App (${randomSuffix})`, completed: true },
{ id: 3, title: `Master TypeScript (${randomSuffix})`, completed: false },
];
};
const getTodosOptions = queryOptions({
queryKey: ['todos'],
queryFn: fetchTodos,
});
export default function TestPage() {
const [filter, setFilter] = useState<'all' | 'completed' | 'incomplete'>(
'all'
);
const queryClient = useQueryClient();
const {
data: todos,
isPending,
isError,
isRefetching,
} = useQuery(getTodosOptions);
if (isPending || isRefetching) return <div>Loading...</div>;
if (isError) return <div>Error fetching todos</div>;
return (
<div className="p-4 text-black bg-white">
<h1 className="text-2xl font-bold mb-4">Todo List Example</h1>
<div className="mb-4">
<button
type="button"
className="mr-2 px-3 py-1 rounded bg-green-500 text-black"
onClick={() => {
queryClient.invalidateQueries({
// THIS DOES NOT SHOW A TYPE ERROR ON 5.66.9
queryKey: getTodosOptions,
});
}}
>
Refresh Data
</button>
<button
type="button"
className={`mr-2 px-3 py-1 rounded ${filter === 'all' ? 'bg-blue-500 text-black' : 'bg-gray-200'}`}
onClick={() => setFilter('all')}
>
All
</button>
<button
type="button"
className={`mr-2 px-3 py-1 rounded ${filter === 'completed' ? 'bg-blue-500 text-black' : 'bg-gray-200'}`}
onClick={() => setFilter('completed')}
>
Completed
</button>
<button
type="button"
className={`px-3 py-1 rounded ${filter === 'incomplete' ? 'bg-blue-500 text-black' : 'bg-gray-200'}`}
onClick={() => setFilter('incomplete')}
>
Incomplete
</button>
</div>
<ul className="space-y-2">
{todos?.map((todo) => (
<li key={todo.id} className="flex items-center p-2 border rounded">
<span
className={`${todo.completed ? 'line-through text-gray-500' : ''}`}
>
{todo.title}
</span>
<span
className={`ml-2 px-2 py-1 text-xs rounded ${todo.completed ? 'bg-green-200' : 'bg-yellow-200'}`}
>
{todo.completed ? 'Completed' : 'Pending'}
</span>
</li>
))}
</ul>
</div>
);
}
Your minimal, reproducible example
Steps to reproduce
Paste the code above into a react app.
Expected behavior
invalidateQueries throws type error when queryKey is not of type array
How often does this bug happen?
Every time
Screenshots or Videos
No response
Platform
macOS Sequoia
Tanstack Query adapter
None
TanStack Query version
5.66.9
TypeScript version
5.5.4
Additional context
No response