Поиск с автодополнением строится вокруг непрерывного потока запросов, которые пользователь генерирует при вводе текста. В классической реализации без специализированных инструментов это приводит к проблемам: избыточные сетевые запросы, гонки ответов, устаревшие результаты и сложное управление состоянием загрузки. TanStack Query позволяет структурировать этот процесс через декларативные запросы, кэширование и управление состоянием сервера.
Автодополнение обычно строится вокруг одного динамического параметра — строки запроса. Каждое изменение ввода пользователя инициирует новый запрос к API:
const fetchSuggestions = async (query) => {
const res = await fetch(`/api/search?q=${encodeURIComponent(query)}`);
if (!res.ok) throw new Error("Network error");
return res.json();
};
В классическом подходе это быстро приводит к лавине запросов. Основная задача — связать ввод пользователя и серверное состояние так, чтобы:
В TanStack Query автодополнение реализуется через динамический
queryKey, зависящий от строки поиска.
import { useQuery } from "@tanstack/react-query";
const useSearchSuggestions = (query) => {
return useQuery({
queryKey: ["autocomplete", query],
queryFn: () => fetchSuggestions(query),
enabled: query.length > 0
});
};
Ключевая идея заключается в том, что каждый уникальный запрос автоматически кэшируется. При повторном вводе уже встречавшегося значения данные берутся из памяти без сетевого запроса.
Проблема частых обновлений решается не внутри TanStack Query, а на уровне UI-логики через debounce. Это предотвращает создание новых query слишком часто.
import { useState, useEffect } from "react";
function useDebouncedValue(value, delay) {
const [debounced, setDebounced] = useState(value);
useEffect(() => {
const id = setTimeout(() => setDebounced(value), delay);
return () => clearTimeout(id);
}, [value, delay]);
return debounced;
}
Использование:
const debouncedQuery = useDebouncedValue(query, 300);
const { data, isLoading } = useQuery({
queryKey: ["autocomplete", debouncedQuery],
queryFn: () => fetchSuggestions(debouncedQuery),
enabled: debouncedQuery.length > 0
});
Такая комбинация снижает нагрузку на сервер и предотвращает избыточные перерендеры.
TanStack Query автоматически кэширует результаты по
queryKey. В контексте автодополнения это особенно важно,
так как пользователи часто вводят похожие или повторяющиеся строки.
Поведение кэша зависит от нескольких параметров:
staleTime — время, в течение которого данные считаются
актуальнымиcacheTime — время хранения данных в памятиgcTime (в новых версиях) — управление сборкой
мусораПример настройки:
useQuery({
queryKey: ["autocomplete", debouncedQuery],
queryFn: () => fetchSuggestions(debouncedQuery),
staleTime: 1000 * 60, // 1 минута
gcTime: 1000 * 60 * 10
});
При высокой частоте повторений это значительно снижает количество сетевых обращений.
В автодополнении критична проблема гонки запросов: пользователь
быстро вводит текст, и ответы приходят в неправильном порядке. TanStack
Query решает это через встроенную поддержку
AbortController.
const fetchSuggestions = async ({ queryKey, signal }) => {
const [, query] = queryKey;
const res = await fetch(`/api/search?q=${query}`, {
signal
});
if (!res.ok) throw new Error("Network error");
return res.json();
};
TanStack Query автоматически передаёт signal, позволяя
браузеру отменять предыдущие запросы при изменении
queryKey.
Корректное отображение состояния критично для UX:
Пример обработки:
const { data, isLoading, isError } = useQuery({
queryKey: ["autocomplete", debouncedQuery],
queryFn: () => fetchSuggestions(debouncedQuery),
enabled: debouncedQuery.length > 0
});
if (debouncedQuery.length === 0) {
return null;
}
if (isLoading) {
return <div>Загрузка...</div>;
}
if (isError) {
return <div>Ошибка загрузки</div>;
}
if (!data?.length) {
return <div>Ничего не найдено</div>;
}
return (
<ul>
{data.map(item => (
<li key={item.id}>{item.title}</li>
))}
</ul>
);
Автодополнение может быть дополнено стратегией предзагрузки. Это уменьшает задержку первого запроса.
import { useQueryClient } from "@tanstack/react-query";
const queryClient = useQueryClient();
const prefetchSuggestions = (query) => {
queryClient.prefetchQuery({
queryKey: ["autocomplete", query],
queryFn: () => fetchSuggestions(query),
staleTime: 1000 * 60
});
};
Предзагрузка часто используется для популярных или трендовых запросов, а также при наведении курсора или фокусе на поле ввода.
Иногда автодополнение зависит от дополнительных параметров: языка, региона или типа поиска.
const useSearch = (query, locale) => {
return useQuery({
queryKey: ["autocomplete", locale, query],
queryFn: () => fetch(`/api/search?q=${query}&locale=${locale}`).then(r => r.json()),
enabled: query.length > 0
});
};
Расширение queryKey обеспечивает изоляцию кэша между
различными контекстами.
Автодополнение часто является частью серверной фильтрации. В таких случаях важно не смешивать локальную фильтрацию и серверные данные.
const useFilteredSuggestions = (query, type) => {
return useQuery({
queryKey: ["autocomplete", type, query],
queryFn: () => fetch(`/api/search?type=${type}&q=${query}`).then(r => r.json()),
enabled: query.length > 2
});
};
Ограничение минимальной длины запроса снижает нагрузку на API и улучшает релевантность.
Структура queryKey является центральным механизмом
синхронизации состояния. Любое изменение ключа:
Типичная ошибка — использование нестабильных объектов:
// плохо
queryKey: ["autocomplete", { query }]
// лучше
queryKey: ["autocomplete", query]
Стабильность ключей напрямую влияет на предсказуемость кэша.
При автодополнении важно избегать «мигания» интерфейса при смене
запроса. Опция placeholderData или
keepPreviousData позволяет сохранять предыдущие результаты
до прихода новых:
import { keepPreviousData } from "@tanstack/react-query";
useQuery({
queryKey: ["autocomplete", debouncedQuery],
queryFn: () => fetchSuggestions(debouncedQuery),
placeholderData: keepPreviousData
});
Это создаёт ощущение непрерывности интерфейса.
Автодополнение может включать сложные сценарии:
Пример параллельных источников:
const useMultiSourceSearch = (query) => {
const local = useQuery({
queryKey: ["local", query],
queryFn: () => fetch(`/api/local?q=${query}`).then(r => r.json()),
enabled: query.length > 1
});
const remote = useQuery({
queryKey: ["remote", query],
queryFn: () => fetch(`/api/remote?q=${query}`).then(r => r.json()),
enabled: query.length > 1
});
return {
data: [...(local.data || []), ...(remote.data || [])],
isLoading: local.isLoading || remote.isLoading
};
};
Такая схема позволяет объединять источники без ручного управления состоянием загрузки каждого запроса.
В системах автодополнения важно не блокировать интерфейс при сбое
API. TanStack Query позволяет реализовать мягкую деградацию через
retry и retryDelay:
useQuery({
queryKey: ["autocomplete", query],
queryFn: () => fetchSuggestions(query),
retry: 1,
retryDelay: 500
});
При этом UI может продолжать отображать предыдущие результаты из кэша, снижая влияние ошибок сети на пользовательский опыт.