Механизм useMutation в RTK Query предназначен для
выполнения операций, изменяющих состояние на сервере: создание,
обновление, удаление сущностей, выполнение командных запросов. В отличие
от useQuery, который ориентирован на получение и
кэширование данных, useMutation работает по событийному
принципу: запрос инициируется явно и не выполняется автоматически при
рендере.
useMutation возвращает кортеж из двух элементов:
Основная особенность заключается в том, что запрос выполняется только после вызова trigger.
const [createUser, result] = useCreateUserMutation();
Здесь:
createUser — функция запуска запросаresult — объект с состоянием выполнения мутацииСостояние мутации включает набор стандартных полей, отражающих жизненный цикл запроса:
data — результат успешного выполненияerror — информация об ошибкеisLoading — запрос выполняется впервыеisSuccess — запрос завершён успешноisError — произошла ошибкаisUninitialized — мутация ещё не запускаласьstatus — строковый статус (pending,
fulfilled, rejected,
uninitialized)const [createPost, result] = useCreatePostMutation();
const {
data,
error,
isLoading,
isSuccess,
isError,
isUninitialized
} = result;
Эти поля позволяют полностью контролировать UI-состояние без дополнительного локального state.
Мутации описываются в createApi через поле
mutation.
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
export const api = createApi({
reducerPath: 'api',
baseQuery: fetchBaseQuery({ baseUrl: '/api' }),
endpoints: (builder) => ({
createPost: builder.mutation({
query: (newPost) => ({
url: '/posts',
method: 'POST',
body: newPost,
}),
}),
}),
});
export const { useCreatePostMutation } = api;
Функция trigger принимает аргумент, который передаётся в
query.
const [createPost] = useCreatePostMutation();
createPost({
title: 'Post title',
content: 'Post content',
});
При вызове происходит:
Функция trigger возвращает промис, содержащий результат выполнения
запроса. RTK Query предоставляет метод unwrap, позволяющий
получить “чистые” данные или выбросить ошибку.
const [createPost] = useCreatePostMutation();
try {
const result = await createPost({ title: 'New post' }).unwrap();
console.log(result);
} catch (err) {
console.error(err);
}
responseerrorМутация может быть вызвана несколько раз подряд. Каждый новый вызов запускает отдельный жизненный цикл.
const [updateUser, result] = useUpdateUserMutation();
const onCl ick = () => {
updateUser({ id: 1, name: 'New name' });
updateUser({ id: 1, name: 'Another name' });
};
Каждый вызов обновляет состояние result, при этом
предыдущий запрос не блокирует следующий.
Параметры передаются напрямую в trigger и становятся входом для
query.
builder.mutation({
query: ({ id, ...patch }) => ({
url: `/users/${id}`,
method: 'PATCH',
body: patch,
}),
});
Вызов:
updateUser({
id: 10,
name: 'Updated name',
});
Одной из ключевых возможностей RTK Query является автоматическое обновление кэша через tags.
endpoints: (builder) => ({
addPost: builder.mutation({
query: (post) => ({
url: '/posts',
method: 'POST',
body: post,
}),
invalidatesTags: ['Posts'],
}),
getPosts: builder.query({
query: () => '/posts',
providesTags: ['Posts'],
}),
});
После успешного выполнения addPost все запросы,
подписанные на Posts, автоматически обновляются.
RTK Query поддерживает возможность обновления UI до завершения запроса.
addPost: builder.mutation({
query: (post) => ({
url: '/posts',
method: 'POST',
body: post,
}),
async onQueryStarted(post, { dispatch, queryFulfilled }) {
const patchResult = dispatch(
api.util.updateQueryData('getPosts', undefined, (draft) => {
draft.push(post);
})
);
try {
await queryFulfilled;
} catch {
patchResult.undo();
}
},
});
Поведение:
Мутации не выполняют автоматический retry по умолчанию, но поведение можно настроить на уровне baseQuery.
baseQuery: fetchBaseQuery({
baseUrl: '/api',
timeout: 5000,
});
Ошибки возвращаются в объекте результата:
if (result.isError) {
console.log(result.error);
}
Состояние мутации можно сбрасывать вручную через
reset.
const [createPost, { reset }] = useCreatePostMutation();
reset();
Это приводит объект состояния к начальному виду:
isUninitialized = truedata = undefinederror = undefinedМутации удобно комбинируются с асинхронной логикой:
const [login] = useLoginMutation();
const handleLogin = async () => {
const user = await login(credentials).unwrap();
localStorage.setItem('token', user.token);
};
При одновременном запуске нескольких мутаций одного типа:
Хотя рассматривается JavaScript, RTK Query изначально типизирован, и сигнатура выглядит следующим образом:
const [trigger, result] = useMutation();
Тип результата зависит от описания endpoint:
TData — тип ответаTArg — тип аргументаTError — тип ошибкиЖизненный цикл мутации проходит стадии:
uninitializedpendingfulfilled или rejectedКаждая стадия отражается в состоянии:
result.status
Мутации часто используются совместно с onSuccess логикой
через useEffect:
const [createPost, result] = useCreatePostMutation();
useEffect(() => {
if (result.isSuccess) {
console.log('Создание завершено');
}
}, [result.isSuccess]);
Trigger возвращает промис, который можно использовать как обычный async-оператор:
createPost(data)
.unwrap()
.then((res) => {
console.log(res);
})
.catch((err) => {
console.error(err);
});