Code review в проектах с использованием TanStack Query отличается от проверки обычного React-кода. Ошибки в работе с серверным состоянием редко проявляются сразу. Большинство проблем становятся заметны только при:
Из-за этого code review должен проверять не только корректность синтаксиса, но и:
Одна из самых частых проблем — создание нестабильных ключей.
Плохой пример:
useQuery({
queryKey: ['users', { page: currentPage, filter }],
queryFn: fetchUsers,
})
Если filter создаётся заново на каждом рендере:
const filter = {
role: selectedRole,
}
то TanStack Query будет воспринимать ключ как новый.
Правильный подход:
const filter = useMemo(() => ({
role: selectedRole,
}), [selectedRole])
useQuery({
queryKey: ['users', filter],
queryFn: fetchUsers,
})
Во время review необходимо проверять:
Хороший проект почти всегда содержит фабрики ключей.
Плохой пример:
['users']
['users', id]
['users', 'list']
['user', id]
Появляется хаос и дублирование.
Хороший пример:
export const userKeys = {
all: ['users'] as const,
lists: () => [...userKeys.all, 'list'] as const,
detail: (id: number) => [...userKeys.all, id] as const,
}
Во время review необходимо проверять:
Плохой пример:
useQuery({
queryKey: ['products'],
queryFn: async () => {
const response = await api.get('/products')
return response.data.map(transformProduct)
},
})
Трансформации начинают смешиваться с загрузкой данных.
Лучше:
const fetchProducts = async () => {
const response = await api.get('/products')
return response.data
}
const useProducts = () => {
return useQuery({
queryKey: productKeys.list(),
queryFn: fetchProducts,
select: transformProducts,
})
}
Во время review важно проверять:
Плохой пример:
queryFn: async () => {
try {
return await api.get('/users')
} catch {
return []
}
}
Ошибка теряется.
Правильный вариант:
queryFn: async () => {
const response = await api.get('/users')
return response.data
}
Во время review необходимо проверять:
Многие разработчики оставляют настройки по умолчанию.
Это приводит к постоянным refetch.
Плохой пример:
useQuery({
queryKey: ['profile'],
queryFn: fetchProfile,
})
Если профиль редко меняется:
useQuery({
queryKey: ['profile'],
queryFn: fetchProfile,
staleTime: 1000 * 60 * 10,
})
Во время review необходимо задавать вопросы:
Часто gcTime вообще не анализируется.
Проблемы:
Особенно важно проверять:
Плохой пример:
useQuery({
queryKey: ['user', userId],
queryFn: () => fetchUser(userId),
})
При userId = undefined запрос всё равно может
выполниться.
Правильно:
useQuery({
queryKey: ['user', userId],
queryFn: () => fetchUser(userId),
enabled: !!userId,
})
Во время review необходимо проверять:
Плохой пример:
const users = data?.map(...)
Трансформация выполняется на каждом рендере.
Лучше:
useQuery({
queryKey: ['users'],
queryFn: fetchUsers,
select: transformUsers,
})
Во время review необходимо проверять:
Самая частая ошибка — слишком широкая инвалидизация.
Плохой пример:
queryClient.invalidateQueries()
Это может перезапустить весь кеш приложения.
Лучше:
queryClient.invalidateQueries({
queryKey: userKeys.detail(userId),
})
Во время review необходимо проверять:
Плохой optimistic update часто ломает консистентность.
Необходимо проверять:
Хороший пример:
onMutate: async (newTodo) => {
await queryClient.cancelQueries({
queryKey: todoKeys.list(),
})
const previous = queryClient.getQueryData(todoKeys.list())
queryClient.setQueryData(todoKeys.list(), old => [
...(old ?? []),
newTodo,
])
return { previous }
}
Плохой пример:
onSuccess: () => {
navigate('/dashboard')
toast.success('Saved')
closeModal()
resetForm()
}
Mutation превращается в центр управления UI.
Во время review необходимо проверять:
Классическая ошибка:
getNextPageParam: lastPage => lastPage.nextPage
Если API вернул undefined некорректно, пагинация ломается.
Во время review необходимо проверять:
Плохой пример:
const items = data.pages.flatMap(page => page.items)
Создание нового массива происходит на каждом рендере.
Лучше:
select: data => ({
...data,
flatItems: data.pages.flatMap(page => page.items),
})
Необходимо искать:
TanStack Query может выполнять refetch при:
Во время review необходимо анализировать:
refetchOnWindowFocus
refetchOnReconnect
refetchOnMount
refetchInterval
Опасные конструкции:
const result = useQuery(...)
Если компонент использует весь объект result — ререндеры становятся слишком частыми.
Лучше:
const { data, isLoading } = useQuery(...)
Во время review важно проверять:
Плохой пример:
/components/UserTable.tsx
/components/UserCard.tsx
/components/UserModal.tsx
В каждом компоненте собственный query.
Лучше:
/features/users/api
/features/users/hooks
/features/users/model
Во время review необходимо проверять:
Плохой пример:
useQuery(...)
напрямую в UI-компоненте.
Лучше:
export const useUsers = () => {
return useQuery(...)
}
Во время review важно проверять:
Плохой пример:
fetch('/users')
axios.get('/users')
apiClient.get('/users')
в одном проекте.
Необходимо проверять:
Во время review необходимо проверять:
Без staleTime после hydration часто происходит мгновенный refetch.
Плохой пример:
dehydrate(queryClient)
без настройки freshness.
Лучше:
useQuery({
queryKey: ['posts'],
queryFn: fetchPosts,
staleTime: 60_000,
})
Плохой пример:
retry: true
Неясно количество повторов.
Лучше:
retry: 3
или:
retry: (count, error) => {
if (error.status === 404) {
return false
}
return count < 3
}
Во время review необходимо проверять:
Если приложение поддерживает offline-first подход, необходимо проверять:
Опасный пример:
queryKey: ['user', token]
Токены могут попасть:
Во время review необходимо проверять:
Во многих приложениях logout реализован неправильно.
Необходимо проверять:
queryClient.clear()
или selective reset.
Иначе новый пользователь может получить старый кеш.
Во время review важно анализировать:
Плохой пример:
useEffect(() => {
fetchUsers()
}, [])
при наличии TanStack Query.
Плохой пример:
const [users, setUsers] = useState([])
useEffect(() => {
setUsers(data)
}, [data])
Плохой пример:
const [loading, setLoading] = useState(false)
вместо:
isLoading
isFetching
isPending
Во время review необходимо проверять:
Плохой пример:
useQuery<any>()
Необходимо анализировать:
Плохой пример:
useData()
useInfo()
useList()
Хороший пример:
useUsersQuery()
useUpdateUserMutation()
useInfinitePostsQuery()
Во время review необходимо проверять:
Необходимо задавать архитектурные вопросы: