How to fix QueryKey must be an array in React

ReactBEGINNERMEDIUMUpdated Dec 10, 2025· 114 views

This error occurs when a non-array value is passed as a queryKey to React Query/TanStack Query hooks. Since v4, queryKey must always be an array, even for simple string keys.

Quick fix

Fixes QueryKey must be an array

// ❌ Wrong - string key
useQuery({
  queryKey: 'todos',
  queryFn: fetchTodos,
})

// ✅ Correct - array key
useQuery({
  queryKey: ['todos'],
  queryFn: fetchTodos,
})
  1. Wrap simple string keys in arrays
  2. Add parameters to query keys as additional array elements
  3. Update query invalidation and management calls
  4. Use query key factories for consistency
react — error
// ❌ Wrong - string key
useQuery({
queryKey: 'todos',
queryFn: fetchTodos,
})
// ✅ Correct - array key
useQuery({
queryKey: ['todos'],
queryFn: fetchTodos,
})
QueryKey must be an array

What this error means

This error indicates that React Query received a queryKey that is not an array. Starting with React Query v4, query keys must be arrays at the top level to ensure type consistency across the library. In earlier versions (v3), query keys could be either strings or arrays. However, this flexibility caused type inconsistencies, especially when using features like predicate filters or accessing query.queryKey in global query functions. To resolve this, the library standardized on array-only keys. When the queryKey is passed to QueryFunctionContext, it's always an array, which makes it easier to work with Array<unknown> on the type level. This consistency is crucial for features like invalidateQueries, getQueryData, and other query management APIs.

Typical symptoms

  • useQuery or useMutation throws an error about queryKey not being an array
  • TypeScript shows type errors when passing string keys to query hooks
  • invalidateQueries or getQueryData fails with queryKey validation errors
  • Console warnings about incorrect queryKey format in development mode
  • Query invalidation or refetching does not work as expected

Common causes

  1. Passing a string directly as queryKey instead of wrapping it in an array (e.g., queryKey: "todos" instead of queryKey: ["todos"])
  2. Migrating from React Query v3 to v4+ without updating queryKey format
  3. Using legacy code examples that show string-based keys from older versions
  4. Typo in the property name (e.g., "gueryKey" instead of "queryKey")
  5. Dynamically generating keys without ensuring they are wrapped in arrays
  6. Copying queryKey values incorrectly when using query client methods

How to fix "QueryKey must be an array"

1Wrap simple string keys in arrays

Convert any string-based queryKey to an array format:

typescript
// ❌ Wrong - string key
useQuery({
  queryKey: 'todos',
  queryFn: fetchTodos,
})

// ✅ Correct - array key
useQuery({
  queryKey: ['todos'],
  queryFn: fetchTodos,
})

This is the most common fix and works for simple queries.

2Add parameters to query keys as additional array elements

When your query depends on variables, include them in the array:

typescript
// Single parameter
useQuery({
  queryKey: ['todo', todoId],
  queryFn: () => fetchTodo(todoId),
})

// Multiple parameters
useQuery({
  queryKey: ['todos', { status, page }],
  queryFn: () => fetchTodos({ status, page }),
})

// Nested arrays are allowed
useQuery({
  queryKey: ['projects', projectId, 'tasks', { completed: true }],
  queryFn: () => fetchProjectTasks(projectId, { completed: true }),
})

Order matters - different orderings of the same items create different keys.

3Update query invalidation and management calls

Ensure all query client methods also use array keys:

typescript
// ❌ Wrong
queryClient.invalidateQueries('todos')
queryClient.getQueryData('todos')
queryClient.setQueryData('todos', newData)

// ✅ Correct
queryClient.invalidateQueries({ queryKey: ['todos'] })
queryClient.getQueryData(['todos'])
queryClient.setQueryData(['todos'], newData)

This applies to invalidateQueries, getQueryData, setQueryData, removeQueries, and other query client methods.

4Use query key factories for consistency

Create a centralized key factory to avoid mistakes:

typescript
// Define your key factory
const todoKeys = {
  all: ['todos'] as const,
  lists: () => [...todoKeys.all, 'list'] as const,
  list: (filters: string) => [...todoKeys.lists(), { filters }] as const,
  details: () => [...todoKeys.all, 'detail'] as const,
  detail: (id: number) => [...todoKeys.details(), id] as const,
}

// Use in queries
useQuery({
  queryKey: todoKeys.detail(todoId),
  queryFn: () => fetchTodo(todoId),
})

// Use in invalidation
queryClient.invalidateQueries({ queryKey: todoKeys.lists() })

This pattern ensures type safety and consistency across your application.

5Run migration codemod for large codebases

If migrating from v3 to v4+, use the official codemod:

bash
# Install jscodeshift if needed
npm install -g jscodeshift

# Run the codemod (adjust paths as needed)
npx jscodeshift ./src --transform ./node_modules/@tanstack/react-query-codemods/dist/v4/key-transformation.js

After running the codemod, format your code:

bash
npm run prettier -- --write .
npm run eslint -- --fix .

Review the changes carefully as codemods may not catch all edge cases.

Advanced notes

Why Arrays Are Required:

React Query's decision to enforce array-only keys was driven by type safety. When keys could be either strings or arrays, the type system had to handle both possibilities (string | Array<unknown>), making it difficult to write type-safe predicate functions and other advanced features. By standardizing on arrays, the library can guarantee consistent types across all APIs.

Query Key Serialization:

React Query serializes query keys to create unique cache entries. As long as the key is serializable (no functions, circular references, etc.), you can use nested objects and arrays. The library uses a deterministic hashing algorithm to convert keys into cache identifiers.

Performance Considerations:

While wrapping a single string in an array adds minimal overhead, the consistency benefits outweigh any marginal performance cost. The library is highly optimized for array-based key lookups.

ESLint Rules:

Consider using the @tanstack/eslint-plugin-query package, which includes rules like "exhaustive-deps" that help catch queryKey mistakes during development. This catches issues like missing dependencies in your query keys before runtime.

Frequently asked questions

What does "QueryKey must be an array" mean?
This error indicates that React Query received a queryKey that is not an array. Starting with React Query v4, query keys must be arrays at the top level to ensure type consistency across the library. In earlier versions (v3), query keys could be either strings or arrays.
How do I fix "QueryKey must be an array"?
This error occurs when a non-array value is passed as a queryKey to React Query/TanStack Query hooks. Since v4, queryKey must always be an array, even for simple string keys.
What causes "QueryKey must be an array"?
Passing a string directly as queryKey instead of wrapping it in an array (e.g., queryKey: "todos" instead of queryKey: ["todos"]) Migrating from React Query v3 to v4+ without updating queryKey format Using legacy code examples that show string-based keys from older versions
Is the "QueryKey must be an array" error serious?
It is a medium-severity error — more of a blocker than a risk. The steps above resolve it.

Official resources & further reading