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

Мутації та Інвалідація: Зміна Даних

Досі ми лише читали дані (GET). Але веб-додатки — це про взаємодію. Ми хочемо створювати, оновлювати та видаляти дані (POST, PUT, DELETE).

Мутації та Інвалідація: Зміна Даних

Досі ми лише читали дані (GET). Але веб-додатки — це про взаємодію. Ми хочемо створювати, оновлювати та видаляти дані (POST, PUT, DELETE).

У TanStack Query за це відповідає хук useMutation.

useMutation vs useQuery

ХарактеристикаuseQueryuseMutation
ЗапускАвтоматично (декларативно)Вручну (імперативно)
ПризначенняЧитання (Read)Запис (Write)
КешуванняКешує результатНе кешує (зазвичай)
Retries3 рази за замовчуванням0 разів (зазвичай небезпечно повторювати POST)

Анатомія Мутації

CreateTodo.tsx
import { useMutation, useQueryClient } from '@tanstack/react-query';

function CreateTodo() {
  const queryClient = useQueryClient();

  const mutation = useMutation({
    mutationFn: (newTodo: { title: string }) => {
      return fetch('/api/todos', {
        method: 'POST',
        body: JSON.stringify(newTodo),
      }).then(res => res.json());
    },
    // Callback на успіх
    onSuccess: (data) => {
      console.log('Todo created!', data);
    },
    // Callback на помилку
    onError: (error) => {
      console.error('Error creating todo:', error);
    },
  });

  return (
    <button
      onClick={() => {
        // Запуск мутації
        mutation.mutate({ title: 'Learn React Query' });
      }}
      disabled={mutation.isPending} // isPending замість isLoading
    >
      {mutation.isPending ? 'Saving...' : 'Add Todo'}
    </button>
  );
}

mutate vs mutateAsync

Ви отримаєте два методи для запуску мутації:

  1. mutate(variables, { onSuccess, ... }):
    • Нічого не повертає (void).
    • Безпечний: якщо станеться помилка, вона буде перехоплена onError в налаштуваннях хука. Додаток не впаде.
    • Рекомендовано для 95% випадків (обробники подій onClick).
  2. mutateAsync(variables):
    • Повертає Promise.
    • Ви повинні самі обробляти помилки через try/catch або .catch().
    • Потрібен, якщо ви хочете чекати завершення мутації, щоб зробити щось ще (наприклад, редірект або закриття модалки).
// Приклад з mutateAsync
const onSubmit = async (data) => {
  try {
    await mutation.mutateAsync(data);
    // Цей код виконається тільки після успіху
    history.push('/todos'); 
  } catch (error) {
    // Обов'язково обробити помилку!
    toast.error(error.message);
  }
};

Інвалідація Запитів (Invalidation)

Це найпотужніша частина TanStack Query. Після того, як ми додали нову Todo, наш список ['todos'] у кеші став застарілим (там немає нового елемента).

Ми повинні сказати Query Client: "Познач ключ ['todos'] як брудний (stale)".

onSuccess: () => {
  // Позначає ВСІ запити, що починаються з ['todos'], як stale.
  // Query автоматично зробить refetch для тих з них, які зараз активні на екрані.
  queryClient.invalidateQueries({ queryKey: ['todos'] });
}

Як це працює?

  1. Мутація завершується успішно.
  2. invalidateQueries знаходить всі активні запити з ключем ['todos'].
  3. Він позначає їх як stale.
  4. Так як вони активні (компонент змонтований), Query миттєво запускає refetch у фоні.
  5. Користувач бачить оновлений список автоматично. Ніяких ручних setTodos([...todos, newTodo])!

Часткове співпадіння ключів (Fuzzy Matching)

queryClient.invalidateQueries({ queryKey: ['todos'] }) інвалідує:

  • ['todos']
  • ['todos', 1]
  • ['todos', 'list', { filter: 'done' }]

Це супер зручно. Ви просто кажете "Все, що стосується todos, застаріло".

Якщо ви хочете інвалідувати тільки точний ключ:

queryClient.invalidateQueries({ 
  queryKey: ['todos'], 
  exact: true 
});

Promise Await в onSuccess

Іноді вам треба дочекатися, поки refetch закінчиться (наприклад, щоб тест пройшов, або щоб закрити модалку тільки коли список оновився).

Поверніть Promise з onSuccess:

onSuccess: async () => {
  // Ми чекаємо, поки invalidate завершить всі refetch
  await queryClient.invalidateQueries({ queryKey: ['todos'] });
  // Тепер дані точно оновлені
  closeModal();
}

Обробка помилок (Side Effects)

Ви можете передавати колбеки як в useMutation (глобально для хука), так і в mutate (локально для виклику).

Порядок виконання:

  1. useMutation.onSuccess
  2. mutate.onSuccess
  3. useMutation.onSettled
  4. mutate.onSettled
Використовуйте useMutation колбеки для речей, які завжди мають статися (інвалідація кешу). Використовуйте mutate колбеки для UI специфіки (показати тост, редірект, закрити форму).

Далі: Оптимістичні Оновлення (Магія UI)

Copyright © 2026