Доки по разработке
This project is maintained by teniryte
useMutation управляет операциями записи (POST/PUT/PATCH/DELETE). В отличие от useQuery, результат мутации не кешируется автоматически, но вы можете синхронизировать его с Query Cache вручную. В v5 добавили массу тонкостей:
mutationKey участвует в useMutationState, девтулзах и persistQueryClient, поэтому стройте иерархию ключей так же тщательно, как для useQuery.gcTime, networkMode, throwOnError, retry, meta, scope позволяют превратить мутацию в строго типизированную команду с нужными SLA.mutationFn теперь получает AbortSignal и meta в третьем аргументе, что удобно для отмены запросов и проброса дополнительных данных.mutationCache даёт возможность подписаться на все мутации и строить глобальные тосты, индикаторы отправки или outbox; вместе с persistQueryClient можно хранить историю неотправленных операций и повторять их после восстановления сети.useMutationState умеет фильтровать незавершённые/ошибочные мутации — отличный инструмент для глобальных индикаторов прогресса и повторной отправки.queryClient.ensureQueryData + mutation.mutateAsync помогает сначала гарантировать наличие связанных данных (например, пользователя), а уже затем выполнять запись.import { useMutation, useQueryClient } from '@tanstack/react-query';
import { createTodo } from './api';
export function CreateTodoForm() {
const queryClient = useQueryClient();
const mutation = useMutation({
mutationKey: ['createTodo'],
mutationFn: createTodo,
onSuccess: async () => {
await queryClient.invalidateQueries({ queryKey: ['todos'], refetchType: 'active' });
},
});
return (
<form
onSubmit={event => {
event.preventDefault();
const form = new FormData(event.currentTarget);
mutation.mutate({ title: form.get('title') as string });
}}
>
<input name="title" disabled={mutation.isPending} />
<button type="submit" disabled={mutation.isPending}>
{mutation.isPending ? 'Сохраняем…' : 'Создать'}
</button>
</form>
);
}
refetchType: 'active' не трогает кэшированные, но не смонтированные запросы.mutation.mutate, а не mutateAsync, чтобы не забыть про event.preventDefault.onMutate (sync/async) — вызывается до mutationFn.onSuccess — после успешного завершения.onError — при ошибке (error, variables, context).onSettled — всегда, независимо от результата.Все обработчики могут быть заданы глобально через QueryClient.
const mutation = useMutation({
mutationFn: updateTodo,
onMutate: async updated => {
await queryClient.cancelQueries({ queryKey: ['todos'] });
const previous = queryClient.getQueryData<Todo[]>(['todos']);
queryClient.setQueryData(['todos'], old =>
old?.map(todo => (todo.id === updated.id ? { ...todo, ...updated } : todo)),
);
return { previous };
},
onError: (_err, _vars, context) => {
if (context?.previous) {
queryClient.setQueryData(['todos'], context.previous);
}
},
onSettled: () => {
queryClient.invalidateQueries({ queryKey: ['todos'] });
},
});
onMutate может вернуть контекст, доступный в onError/onSettled.onMutate не только предыдущие данные, но и функцию rollback, чтобы в onError просто вызвать context.rollback?.().mutation.setState({ status: 'success', data: ... }), чтобы UI мгновенно переключал состояния без ожидания сетевого ответа.useInfiniteQuery) используйте queryClient.setQueriesData с pageParam и аккуратно обновляйте нужную страницу.mutation.addRevertListener(fn) (через mutationCache), чтобы откатывать каскадно.mutate vs mutateAsyncmutate(variables, { onSuccess }) — callback-стиль.mutateAsync(variables) — возвращает Promise, удобно для async/await форм.await mutation.mutateAsync(formData);
toast.success('Задача обновлена');
useMutation({
mutationFn: toggleTodo,
onSuccess: (data) => {
queryClient.setQueryData(['todo', data.id], data);
queryClient.setQueriesData(
{ queryKey: ['todos'] },
old => old?.map(todo => (todo.id === data.id ? data : todo)),
);
},
});
Это уменьшает количество запросов в сеть и делает UI мгновенным.
invalidateQueries по связанным ключам (['project', projectId]).refetchType: 'none' и queryClient.setQueryData, а сервер «догонять» бекграундным prefetchQuery.meta и конфигурацияuseMutation({
mutationFn: ({ title }) => client.post('/todos', { title }),
meta: { feature: 'todo-create' },
onSuccess: (_data, _vars, _ctx, mutation) => {
analytics.track('todo_created', mutation.meta);
},
});
metamutationFn: async (vars, { signal, meta }) => ... — можно передать meta.traceId, segment, optimisticResponse.mutationCache.subscribe получает mutation.options.meta, что позволяет строить глобальные уведомления с категорией события.useMutationState({ filters: { meta: { feature: 'todo-create' }, status: 'pending' } }) — быстро показывает, что конкретно сейчас отправляется.status: 'idle' | 'pending' | 'error' | 'success'isPending, isSuccess, isError, isIdle, isPausedfailureCount, failureReason, submittedAtmutationCache и onMutate, чтобы хранить историю.persistQueryClient + onlineManager, чтобы повторить мутации после восстановления сети.mutationKey.['todos'] без фильтров, если обновляете только один элемент — используйте invalidateQueries({ queryKey: ['todos'], exact: true }) или setQueryData.mutation.reset() при повторном открытии модалки.const pendingSaves = useMutationState({
filters: { status: 'pending', mutationKey: ['todo', 'save'] },
});
return <Spinner hidden={!pendingSaves.length} />;
useMutationState подписывается на кэш мутаций, не требует проп-дриллинга и автоматически очищается по gcTime.
const updateTodo = useMutation({ mutationKey: ['todos', 'update'], mutationFn: patchTodo });
const revalidateStats = useMutation({ mutationKey: ['stats', 'sync'], mutationFn: syncStats });
await updateTodo.mutateAsync(payload);
await revalidateStats.mutateAsync({ projectId: payload.projectId });
onSuccess.Promise.allSettled.mutation.isPending перед следующим вызовом или используйте p-retry/p-queue.bulk update) используйте meta.batchId и храните промежуточные ответы в mutationCache.const mutation = useMutation({
mutationFn: async (payload, { signal }) => {
return client.post('/upload', payload, { signal });
},
});
useEffect(() => {
return () => mutation.reset(); // отменит активную загрузку при размонтировании формы
}, [mutation]);
queryClient.getQueryData, чтобы не хранить лишнее состояние.useQuery + useMutation в одном хуке, возвращая data, mutate и isPending. Это упрощает повторное использование бизнес-логики.mutationCache и видеть историю всех POST/PUT/DELETE.queryClient.getMutationCache().find({ mutationKey }) помогает собрать статистику или принудительно завершить/удалить мутацию.await waitFor(() => expect(mutation.isSuccess).toBe(true)), а затем проверяйте, что правильные данные оказались в queryClient.