This skill builds on core/fetch-and-observe-queries and core/seed-placeholder-select-and-transform-data.
Setup
import { useQuery } from '@tanstack/react-query'
export function TodoCount() {
const { data } = useQuery({
queryKey: ['todos'],
queryFn: async () => [{ id: 1 }, { id: 2 }],
select: (todos) => todos.length,
})
return <p>{data ?? 0}</p>
}
Hooks and Components
Destructure only used result fields
import { useQuery } from '@tanstack/react-query'
export function Todos() {
const { data, isFetching } = useQuery({
queryKey: ['todos'],
queryFn: async () => [{ id: 1 }],
})
return (
<pre>
{isFetching ? 'Refreshing ' : ''}
{JSON.stringify(data ?? [])}
</pre>
)
}
Keep Vue query data immutable
import { ref } from 'vue'
export function editableTodo(todo: { id: number; title: string }) {
return ref({ ...todo })
}
Use stable members in deps
import * as React from 'react'
import { useQuery } from '@tanstack/react-query'
export function TodosEffect() {
const { data } = useQuery({
queryKey: ['todos'],
queryFn: async () => [{ id: 1 }],
})
React.useEffect(() => console.log(data?.length ?? 0), [data])
return null
}
Common Mistakes
MEDIUM Rest destructuring
Wrong:
import { useQuery } from '@tanstack/react-query'
export function useTodos() {
const { data, ...rest } = useQuery({
queryKey: ['todos'],
queryFn: async () => [{ id: 1 }],
})
return { data, rest }
}
Correct:
import { useQuery } from '@tanstack/react-query'
export function useTodos() {
const { data, isFetching, error } = useQuery({
queryKey: ['todos'],
queryFn: async () => [{ id: 1 }],
})
return { data, isFetching, error }
}
Rest destructuring touches every tracked property and disables fine-grained render tracking.
Source: TanStack/query:docs/eslint/no-rest-destructuring.md
HIGH Query result in hook deps
Wrong:
import * as React from 'react'
import { useQuery } from '@tanstack/react-query'
export function Todos() {
const query = useQuery({
queryKey: ['todos'],
queryFn: async () => [{ id: 1 }],
})
React.useEffect(() => console.log(query.data), [query])
return null
}
Correct:
import * as React from 'react'
import { useQuery } from '@tanstack/react-query'
export function Todos() {
const { data } = useQuery({
queryKey: ['todos'],
queryFn: async () => [{ id: 1 }],
})
React.useEffect(() => console.log(data), [data])
return null
}
The top-level query result object is not referentially stable; destructured members are tracked.
Source: TanStack/query:docs/eslint/no-unstable-deps.md
HIGH Vue v-model mutates query result
Wrong:
<script setup lang="ts">
const todo = { id: 1, title: 'Ship' }
</script>
<template><input v-model="todo.title" /></template>
Correct:
<script setup lang="ts">
import { ref } from 'vue'
const editableTodo = ref({ id: 1, title: 'Ship' })
</script>
<template><input v-model="editableTodo.title" /></template>
Vue Query results are immutable; make a mutable copy for form state.
Source: TanStack/query:docs/framework/vue/reactivity.md
See also: compositions/enforce-query-best-practices-with-eslint for lint rules that protect render tracking.