TanStack Query: Майстерність Керування Станом Сервера

Просунуті Патерни та Оптимізація

Ви вже вмієте робити базові речі. Тепер перейдемо до технік "чорного поясу". Ці патерни допоможуть вирішити складні архітектурні завдання та оптимізувати продуктивність.

Просунуті Патерни та Оптимізація

Ви вже вмієте робити базові речі. Тепер перейдемо до технік "чорного поясу". Ці патерни допоможуть вирішити складні архітектурні завдання та оптимізувати продуктивність.

Залежні Запити (Dependent Queries)

Часта ситуація: вам треба отримати користувача, а потім отримати його проекти, використовуючи userId.

В useEffect це пекло вкладеності. В TanStack Query це елегантно робиться через опцію enabled.

// 1. Отримуємо користувача
const { data: user } = useQuery({
  queryKey: ['user', email],
  queryFn: () => fetchUser(email),
});

const userId = user?.id;

// 2. Отримуємо проекти ТІЛЬКИ коли є userId
const { data: projects } = useQuery({
  queryKey: ['projects', userId],
  queryFn: () => fetchProjects(userId),
  // Запит не запуститься, поки enabled === false
  enabled: !!userId, 
});

Поки user завантажується, userId буде undefined, і другий запит стоятиме на паузі (status: pending, fetchStatus: idle). Як тільки user завантажиться, другий запит автоматично полетить.

Трансформація Даних (select)

Іноді бекенд повертає величезний об'єкт, а вам потрібен лише маленький шматочок або відфільтрований список. Ви можете зробити це в компоненті, але тоді це буде перераховуватися при кожному рендері.

Опція select дозволяє трансформувати дані всередині Query. Результат буде мемоізований.

const { data: userNames } = useQuery({
  queryKey: ['users'],
  queryFn: fetchUsers, // Повертає User[]
  // Трансформуємо User[] -> string[]
  select: (users) => users.map(u => u.name),
});
select виконується тільки тоді, коли data змінилася. Це чудова оптимізація продуктивності.

Initial Data (Початкові Дані)

Якщо у вас вже є дані (наприклад, ви перейшли зі списку на детальну сторінку, і у вас вже є title та summary поста), ви можете показати їх миттєво.

initialData vs placeholderData

  1. initialData: Вважається "справжніми" даними. Вони записуються в кеш. Якщо staleTime ще не вийшов, запит навіть не полетить.
    • Use case: Передача даних з SSR (Next.js getServerSideProps).
  2. placeholderData: "Фейкові" дані. Вони не записуються в кеш. Вони зникають, як тільки прийдуть справжні дані.
    • Use case: Показати часткові дані зі списку, поки вантажаться повні дані.
// Ми в списку (List View) вже маємо короткий опис поста
const { data: post } = useQuery({
  queryKey: ['post', id],
  queryFn: () => fetchPost(id),
  initialData: () => {
    // Шукаємо пост в кеші списку 'posts'
    return queryClient.getQueryData(['posts'])?.find(p => p.id === id);
  },
  // Якщо ми знайшли дані, вважаємо їх свіжими 30 секунд
  initialDataUpdatedAt: () => queryClient.getQueryState(['posts'])?.dataUpdatedAt,
});

Скасування Запитів (Request Cancellation)

За замовчуванням TanStack Query скасовує запит, якщо компонент демонтується або ключ змінюється (щоб не обробляти застарілу відповідь).

Щоб це працювало, ваша queryFn повинна підтримувати AbortSignal.

const fetchTodos = async ({ signal }) => {
  const response = await fetch('/todos', {
    // Передаємо сигнал у fetch
    signal, 
  });
  return response.json();
};

Axios підтримує це так:

const fetchTodos = async ({ signal }) => {
  const response = await axios.get('/todos', {
    signal,
  });
  return response.data;
};

Тепер, якщо користувач швидко клікає по категоріях, браузер скасує попередні HTTP-запити, заощаджуючи трафік.

Динамічні Паралельні Запити (useQueries)

useQuery не можна викликати в циклі (правила хуків). Що робити, якщо у нас є масив ID [1, 2, 3], і нам треба завантажити кожен окремо?

Використовуйте useQueries (множина).

const ids = [1, 2, 3];

const results = useQueries({
  queries: ids.map(id => ({
    queryKey: ['post', id],
    queryFn: () => fetchPost(id),
    staleTime: Infinity,
  })),
});

// results — це масив результатів запитів
const isLoading = results.some(result => result.isPending);

Це дозволяє запускати довільну кількість запитів паралельно, дотримуючись правил хуків.

Далі: Архітектура та Best Practices

Copyright © 2026