Перейти к содержимому

3. Query: useQuery и fetching

useQuery — главный хук TanStack Query для получения данных. Он принимает объект конфигурации и возвращает объект с данными, статусом и вспомогательными флагами.

const result = useQuery({
queryKey: ['users'],
queryFn: () => fetch('/api/users').then(r => r.json()),
})

queryKey — массив значений, уникально идентифицирующий запрос в кэше. Это один из самых важных концептов:

// Простой ключ
useQuery({ queryKey: ['users'], queryFn: getUsers })
// Ключ с параметрами (разные пользователи — разные запросы в кэше)
useQuery({ queryKey: ['user', userId], queryFn: () => getUser(userId) })
// Ключ с фильтрами
useQuery({ queryKey: ['users', { status: 'active', page: 1 }], queryFn: getUsers })

При изменении queryKey автоматически выполняется новый запрос. Это ключевой механизм реактивности.

queryFn — асинхронная функция, которая получает данные. Она должна либо вернуть данные, либо выбросить ошибку:

// С fetch
const queryFn = async () => {
const response = await fetch('/api/users')
if (!response.ok) throw new Error('Ошибка загрузки')
return response.json()
}
// С axios
const queryFn = () => axios.get('/api/users').then(r => r.data)
// Получение параметров из контекста
const queryFn = ({ queryKey }) => {
const [_key, userId] = queryKey
return getUser(userId)
}

useQuery возвращает богатый объект с информацией о состоянии запроса:

const {
data, // Данные (undefined до загрузки)
error, // Объект ошибки (null при успехе)
status, // 'pending' | 'error' | 'success'
isLoading, // true только при первой загрузке (нет данных в кэше)
isFetching, // true при любой загрузке (включая фоновую)
isError, // true при ошибке
isSuccess, // true при успехе
isPending, // true пока нет данных
refetch, // Функция принудительного обновления
dataUpdatedAt, // Timestamp последнего обновления
} = useQuery({ queryKey, queryFn })
  • isLoadingtrue только при первой загрузке (нет данных в кэше)
  • isFetchingtrue при любом активном запросе (включая фоновые обновления)

Используйте isFetching для индикатора фоновой загрузки, isLoading — для скелетона при первой загрузке.

function UserList() {
const { data, isLoading, isError, error, isFetching } = useQuery({
queryKey: ['users'],
queryFn: getUsers,
})
if (isLoading) return <Skeleton />
if (isError) return <ErrorMessage message={error.message} />
return (
<>
{isFetching && <RefreshIndicator />}
<ul>{data.map(u => <li key={u.id}>{u.name}</li>)}</ul>
</>
)
}