Введение
В React Query запросы к API, которые изменяют данные (добавляют, обновляют или удаляют ресурсы), называются мутациями. Они отличаются от запросов queries, которые только читают данные.
Примером мутаций могут быть:
- Создание нового поста
POST - Обновление профиля пользователя
PUTилиPATCH - Удаление элемента
DELETE
Мутации полезны в сценариях, когда вы хотите:
- Обновить состояние после успешного изменения данных на сервере.
- Обрабатывать оптимистичные обновления (показывать изменения без ожидания ответа от сервера).
- Откатывать обновления в случае ошибки.
Использование useMutation
Самая захватывающая часть React Query — это использование хука useMutation для работы с мутациями. Давайте рассмотрим его на практике с простым примером: создание нового поста.
Начнем с настройки. Пусть у нас есть API, который принимает POST запросы для добавления постов.
Пример 1. Создание поста
import { useMutation } from '@tanstack/react-query';
import axios from 'axios';
// Тип данных для нового поста
interface Post {
id: number;
title: string;
content: string;
}
// Функция для отправки POST-запроса
const createPost = async (newPost: Omit<Post, 'id'>): Promise<Post> => {
const response = await axios.post<Post>('/api/posts', newPost);
return response.data;
};
const CreatePostComponent = () => {
// Инициализация мутации
const mutation = useMutation({
mutationFn: createPost,
onSuccess: (data) => {
console.log('Пост успешно создан:', data);
},
onError: (error) => {
console.error('Ошибка при создании поста:', error);
},
});
const handleCreatePost = () => {
// Данные для нового поста
const newPost = { title: 'Привет, React Query!', content: 'Это мой первый пост.' };
mutation.mutate(newPost);
};
return (
<div>
<button onClick={handleCreatePost} disabled={mutation.isPending}>
{mutation.isPending ? 'Создание...' : 'Создать пост'}
</button>
{mutation.isError && <p>Ошибка: {mutation.error?.message}</p>}
{mutation.isSuccess && <p>Пост создан успешно!</p>}
</div>
);
};
export default CreatePostComponent;
Разбор кода
Инициализация
useMutation:- Мы передаем
createPostкак "мутирующую" функцию, которая выполняетPOSTзапрос. - Также указываем опции
onSuccessиonErrorдля обработки успешного выполнения и ошибок.
- Мы передаем
Вызов
mutation.mutate:- Когда пользователь нажимает кнопку, мы вызываем
mutation.mutate(newPost), передавая данные нового поста.
- Когда пользователь нажимает кнопку, мы вызываем
Состояния мутации:
mutation.isLoadingпоказывает, что запрос в процессе.mutation.isSuccessуказывает, что запрос выполнен успешно.mutation.isErrorобрабатывает случаи ошибки.
Управление состоянием мутации
Мутации, как и запросы, имеют несколько состояний:
isLoading— мутация выполняется.isSuccess— мутация выполнена успешно.isError— ошибка при выполнении мутации.isIdle— мутация не была вызвана.
Эти состояния помогают гибко управлять интерфейсом и взаимодействиями пользователей. Например, вы можете блокировать кнопку отправки, пока запрос не завершится, или показать индикатор загрузки.
Пример 2. Удаление поста с подтверждением
Теперь рассмотрим, как использовать мутацию для удаления поста. Мы добавим подтверждение перед удалением.
import { useMutation } from '@tanstack/react-query';
import axios from 'axios';
const deletePost = async (id: number): Promise<void> => {
await axios.delete(`/api/posts/${id}`);
};
const PostItem = ({ id, title }: { id: number; title: string }) => {
// Инициализация мутации с пробросом ID при вызове
const mutation = useMutation({
mutationFn: deletePost,
onSuccess: () => {
console.log(`Пост с ID ${id} удален`);
},
});
const handleDelete = () => {
if (window.confirm('Вы уверены, что хотите удалить пост?')) {
mutation.mutate(id); // Передаём ID поста как аргумент мутации
}
};
return (
<div>
<h3>{title}</h3>
<button onClick={handleDelete} disabled={mutation.isPending}>
{mutation.isPending ? 'Удаляется...' : 'Удалить'}
</button>
</div>
);
};
export default PostItem;
Разбор кода
- Мы передаем в
useMutationфункциюdeletePost, которая принимает ID поста. - Перед вызовом
mutation.mutateпроверяем подтверждение пользователя (черезwindow.confirm). - По завершении удаления вызывается
onSuccessдля дополнительной логики (например, обновления состояния приложения).
Обработка оптимистичных обновлений
Оптимистичные обновления — это метод, при котором UI обновляется до того, как сервер подтвердит изменения. Если серверная операция завершится неудачно, изменения откатываются.
Пример 3. Оптимистичное обновление списка постов
Предположим, у нас есть локальный список постов, который нужно обновить мгновенно после успешного создания нового поста.
import { useMutation, useQueryClient } from '@tanstack/react-query';
import axios from 'axios';
const createPostWithOptimisticUpdate = async (
newPost: Omit<Post, 'id'>
): Promise<Post> => {
const response = await axios.post<Post>('/api/posts', newPost);
return response.data;
};
const PostsList = () => {
const queryClient = useQueryClient();
const mutation = useMutation({
mutationFn: createPostWithOptimisticUpdate,
onMutate: async (newPost) => {
// Отменяем любые активные запросы для предотвращения конфликтов
await queryClient.cancelQueries({ queryKey: ['posts'] });
// Сохраняем текущее состояние кэша
const previousPosts = queryClient.getQueryData<Post[]>(['posts']);
// Оптимистично обновляем кэш
queryClient.setQueryData<Post[]>(['posts'], (old) => [
...(old || []),
{ ...newPost, id: Date.now() }, // Генерируем временный ID
]);
// Возвращаем предыдущий кэш для восстановления в случае ошибки
return { previousPosts };
},
onError: (err, newPost, context) => {
// Восстанавливаем старое состояние из сохраненного контекста
if (context?.previousPosts) {
queryClient.setQueryData(['posts'], context.previousPosts);
}
},
onSettled: () => {
// Повторно запрашиваем данные после завершения мутации
queryClient.invalidateQueries({ queryKey: ['posts'] });
},
});
const handleCreatePost = () => {
mutation.mutate({
title: 'Оптимистичный пост',
content: 'Это создано мгновенно!',
});
};
return (
<button onClick={handleCreatePost} disabled={mutation.isPending}>
{mutation.isPending ? 'Создание...' : 'Создать пост'}
</button>
);
};
export default PostsList;
Как это работает:
- В
onMutateмы добавляем пост в локальный кэш сразу после вызова мутации. - Если запрос завершился с ошибкой, кэш восстанавливается из сохраненного состояния.
- По завершении мутации (успешно или с ошибкой) мы вызываем
invalidateQueries, чтобы синхронизировать данные с сервером.
Обработка ошибок в мутациях
React Query предоставляет встроенную обработку ошибок. Например:
mutation.errorвозвращает данные об ошибке.- Вы можете использовать
onErrorдля выполнения определенных действий при провале.
Простой пример:
mutation.mutate(data, {
onError: (error) => {
console.error("Мутация провалилась:", error);
},
});
Применение в реальных проектах
Корректное использование мутаций обеспечивает:
- Быстрое и отзывчивое обновление интерфейса (оптимистичные обновления).
- Надежную обработку ошибок и возврат в предыдущее состояние.
- Удобное управление состоянием при взаимодействии с сервером.
Например, в приложениях электронной коммерции мутации можно использовать для обработки корзины покупок, обновления профиля пользователя или регистрации на платформе.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ