В RTK Query отправка данных на сервер реализуется через mutation endpoints — специальный тип запросов, предназначенный для операций изменения состояния на сервере: создания, обновления и удаления сущностей.
В отличие от query, мутации:
Классическая структура описания мутации внутри createApi
строится через builder.mutation.
Базовая настройка API слоя, включающего отправку данных:
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
export const api = createApi({
reducerPath: 'api',
baseQuery: fetchBaseQuery({
baseUrl: 'https://example.com/api',
}),
tagTypes: ['Post'],
endpoints: (builder) => ({
createPost: builder.mutation({
query: (newPost) => ({
url: '/posts',
method: 'POST',
body: newPost,
}),
}),
}),
});
Ключевой момент: мутация описывается как функция query,
возвращающая объект запроса.
Для выполнения отправки данных используется автоматически сгенерированный хук:
const [createPost, { data, error, isLoading }] =
api.useCreatePostMutation();
Структура:
createPost — функция запуска запросаdata — результат успешной операцииerror — ошибка запросаisLoading — состояние выполненияВызов мутации осуществляется вручную:
const handleSubmit = async () => {
await createPost({
title: 'Новая статья',
body: 'Контент статьи',
userId: 1,
});
};
Функция возвращает Promise, что позволяет
использовать async/await.
По умолчанию результат мутации оборачивается в Redux Toolkit action.
Для получения чистого результата используется unwrap():
const handleSubmit = async () => {
try {
const result = await createPost({
title: 'Тест',
body: 'Контент',
}).unwrap();
console.log('Созданный объект:', result);
} catch (err) {
console.error('Ошибка создания:', err);
}
};
unwrap():
createUser: builder.mutation({
query: (user) => ({
url: '/users',
method: 'POST',
body: user,
}),
});
updateUser: builder.mutation({
query: (user) => ({
url: `/users/${user.id}`,
method: 'PUT',
body: user,
}),
});
patchUser: builder.mutation({
query: ({ id, ...patch }) => ({
url: `/users/${id}`,
method: 'PATCH',
body: patch,
}),
});
deleteUser: builder.mutation({
query: (id) => ({
url: `/users/${id}`,
method: 'DELETE',
}),
});
После изменения данных важно синхронизировать кеш. RTK Query использует систему тегов.
getPosts: builder.query({
query: () => '/posts',
providesTags: ['Post'],
}),
createPost: builder.mutation({
query: (post) => ({
url: '/posts',
method: 'POST',
body: post,
}),
invalidatesTags: ['Post'],
}),
Механизм:
providesTags — помечает данные как принадлежащие
тегу,invalidatesTags — инициирует повторный запрос зависимых
данных.Мутации часто требуют параметров URL:
updateComment: builder.mutation({
query: ({ postId, commentId, text }) => ({
url: `/posts/${postId}/comments/${commentId}`,
method: 'PUT',
body: { text },
}),
});
RTK Query предоставляет детализированные флаги состояния:
const [
updateUser,
{
isLoading,
isSuccess,
isError,
error,
reset,
},
] = api.useUpdateUserMutation();
Поведение:
isLoading — запрос выполняетсяisSuccess — успешное завершениеisError — ошибкаreset — сброс состояния мутацииТипичный сценарий интеграции с формой:
const [createPost, { isLoading }] = api.useCreatePostMutation();
const onSub mit = async (e) => {
e.preventDefault();
const formData = new FormData(e.target);
await createPost({
title: formData.get('title'),
body: formData.get('body'),
});
};
Ошибки доступны через error объект:
const [
createPost,
{ error }
] = api.useCreatePostMutation();
if (error) {
console.log(error.status);
console.log(error.data);
}
Часто структура ошибки зависит от baseQuery, например
fetchBaseQuery.
createPost: builder.mutation({
query: (post) => ({
url: '/posts',
method: 'POST',
body: post,
headers: {
Authorization: 'Bearer token',
},
}),
});
fetchBaseQuery({
baseUrl: '/api',
prepareHeaders: (headers, { getState }) => {
const token = getState().auth.token;
if (token) {
headers.set('authorization', `Bearer ${token}`);
}
return headers;
},
});
RTK Query позволяет обновлять UI до ответа сервера:
updatePost: builder.mutation({
query: ({ id, ...patch }) => ({
url: `/posts/${id}`,
method: 'PATCH',
body: patch,
}),
async onQueryStarted({ id, ...patch }, { dispatch, queryFulfilled }) {
const patchResult = dispatch(
api.util.updateQueryData('getPost', id, (draft) => {
Object.assign(draft, patch);
})
);
try {
await queryFulfilled;
} catch {
patchResult.undo();
}
},
});
createPost: builder.mutation({
query: (post) => ({
url: '/posts',
method: 'POST',
body: post,
}),
transformResponse: (response) => {
return {
...response,
createdAt: Date.now(),
};
},
});
Один endpoint может использоваться в разных частях приложения:
const [createPost] = api.useCreatePostMutation();
const [updatePost] = api.useUpdatePostMutation();
const [deletePost] = api.useDeletePostMutation();
Каждый хук генерируется автоматически и полностью независим по состоянию.
RTK Query позволяет запускать мутации через store:
store.dispatch(
api.endpoints.createPost.initiate({
title: 'Тест',
body: 'Контент',
})
);
Несколько последовательных мутаций:
const [createUser] = api.useCreateUserMutation();
const [createProfile] = api.useCreateProfileMutation();
const handleCreate = async () => {
const user = await createUser({ name: 'Alex' }).unwrap();
await createProfile({
userId: user.id,
bio: 'Developer',
});
};
При необходимости повторного запуска используется ключ
fixedCacheKey:
const [sendMessage] = api.useSendMessageMutation({
fixedCacheKey: 'chat-send',
});
Позволяет сохранять состояние мутации между компонентами.
Типичный цикл мутации:
query: () => ({
url: '/posts',
method: 'POST',
});
Без body сервер может вернуть 400.
Использование GET вместо POST в mutation
ломает семантику RTK Query.
После создания/обновления данных UI может остаться устаревшим при
отсутствии invalidatesTags.
RTK Query API slice становится единым источником всех операций изменения данных, исключая необходимость ручного Redux reducer для сетевых операций.
UI часто строится на основе:
isLoading — блокировка формыisError — отображение ошибкиisSuccess — редирект или уведомлениеRTK Query обеспечивает:
const [createPost] = api.useCreatePostMutation();
const promise = createPost(data);
promise.abort();
Позволяет прервать отправку при необходимости.