Отправка данных на сервер

В RTK Query отправка данных на сервер реализуется через mutation endpoints — специальный тип запросов, предназначенный для операций изменения состояния на сервере: создания, обновления и удаления сущностей.

В отличие от query, мутации:

  • выполняются вручную (по триггеру),
  • не кешируются как источник истины,
  • часто инвалидируют связанные данные,
  • возвращают объект состояния запроса и функцию запуска.

Классическая структура описания мутации внутри createApi строится через builder.mutation.


Определение API с мутациями

Базовая настройка 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, возвращающая объект запроса.


Использование useMutation хука

Для выполнения отправки данных используется автоматически сгенерированный хук:

const [createPost, { data, error, isLoading }] =
  api.useCreatePostMutation();

Структура:

  • createPost — функция запуска запроса
  • data — результат успешной операции
  • error — ошибка запроса
  • isLoading — состояние выполнения

Отправка данных на сервер

Вызов мутации осуществляется вручную:

const handleSubmit = async () => {
  await createPost({
    title: 'Новая статья',
    body: 'Контент статьи',
    userId: 1,
  });
};

Функция возвращает Promise, что позволяет использовать async/await.


unwrap() и работа с результатом

По умолчанию результат мутации оборачивается в Redux Toolkit action. Для получения чистого результата используется unwrap():

const handleSubmit = async () => {
  try {
    const result = await createPost({
      title: 'Тест',
      body: 'Контент',
    }).unwrap();

    console.log('Созданный объект:', result);
  } catch (err) {
    console.error('Ошибка создания:', err);
  }
};

unwrap():

  • возвращает payload при успехе,
  • выбрасывает исключение при ошибке,
  • упрощает обработку через try/catch.

Отправка данных с типовыми HTTP методами

POST — создание сущности

createUser: builder.mutation({
  query: (user) => ({
    url: '/users',
    method: 'POST',
    body: user,
  }),
});

PUT — полное обновление

updateUser: builder.mutation({
  query: (user) => ({
    url: `/users/${user.id}`,
    method: 'PUT',
    body: user,
  }),
});

PATCH — частичное обновление

patchUser: builder.mutation({
  query: ({ id, ...patch }) => ({
    url: `/users/${id}`,
    method: 'PATCH',
    body: patch,
  }),
});

DELETE — удаление

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 — сброс состояния мутации

Отправка формы с RTK Query

Типичный сценарий интеграции с формой:

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;
  },
});

Optimistic updates при отправке данных

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();
    }
  },
});

Возврат кастомных данных через transformResponse

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();

Каждый хук генерируется автоматически и полностью независим по состоянию.


Отправка данных без React (dispatch)

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',
});

Позволяет сохранять состояние мутации между компонентами.


Поток данных при отправке

Типичный цикл мутации:

  1. вызов trigger-функции
  2. формирование HTTP запроса
  3. выполнение baseQuery
  4. обработка ответа
  5. обновление cache / invalidation
  6. обновление состояния хука

Особенности поведения мутаций

  • не выполняются автоматически
  • не подписываются на кеш как query
  • могут вызывать side effects через lifecycle hooks
  • поддерживают optimistic updates
  • интегрируются с Redux DevTools как actions

Типовые ошибки при отправке данных

Отсутствие тела запроса

query: () => ({
  url: '/posts',
  method: 'POST',
});

Без body сервер может вернуть 400.


Неверный метод

Использование GET вместо POST в mutation ломает семантику RTK Query.


Отсутствие invalidation

После создания/обновления данных UI может остаться устаревшим при отсутствии invalidatesTags.


Паттерны архитектуры отправки данных

CRUD слой через RTK Query

  • createX — POST
  • updateX — PUT/PATCH
  • deleteX — DELETE
  • getX — query

Централизованная логика API

RTK Query API slice становится единым источником всех операций изменения данных, исключая необходимость ручного Redux reducer для сетевых операций.


Состояние мутации как источник UI логики

UI часто строится на основе:

  • isLoading — блокировка формы
  • isError — отображение ошибки
  • isSuccess — редирект или уведомление

Асинхронность и очередь выполнения

RTK Query обеспечивает:

  • дедупликацию запросов
  • контроль конкурентных мутаций
  • отмену устаревших запросов при смене состояния компонента

Использование abort

const [createPost] = api.useCreatePostMutation();

const promise = createPost(data);

promise.abort();

Позволяет прервать отправку при необходимости.