JavaRush /Курсы /Модуль 3: React /Создание мутаций для изменения данных с помощью React Que...

Создание мутаций для изменения данных с помощью React Query

Модуль 3: React
8 уровень , 5 лекция
Открыта

Введение

В 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;

Разбор кода

  1. Инициализация useMutation:

    • Мы передаем createPost как "мутирующую" функцию, которая выполняет POST запрос.
    • Также указываем опции onSuccess и onError для обработки успешного выполнения и ошибок.
  2. Вызов mutation.mutate:

    • Когда пользователь нажимает кнопку, мы вызываем mutation.mutate(newPost), передавая данные нового поста.
  3. Состояния мутации:

    • 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; 

Разбор кода

  1. Мы передаем в useMutation функцию deletePost, которая принимает ID поста.
  2. Перед вызовом mutation.mutate проверяем подтверждение пользователя (через window.confirm).
  3. По завершении удаления вызывается 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; 

Как это работает:

  1. В onMutate мы добавляем пост в локальный кэш сразу после вызова мутации.
  2. Если запрос завершился с ошибкой, кэш восстанавливается из сохраненного состояния.
  3. По завершении мутации (успешно или с ошибкой) мы вызываем invalidateQueries, чтобы синхронизировать данные с сервером.

Обработка ошибок в мутациях

React Query предоставляет встроенную обработку ошибок. Например:

  • mutation.error возвращает данные об ошибке.
  • Вы можете использовать onError для выполнения определенных действий при провале.

Простой пример:

mutation.mutate(data, {
  onError: (error) => {
    console.error("Мутация провалилась:", error);
  },
});

Применение в реальных проектах

Корректное использование мутаций обеспечивает:

  • Быстрое и отзывчивое обновление интерфейса (оптимистичные обновления).
  • Надежную обработку ошибок и возврат в предыдущее состояние.
  • Удобное управление состоянием при взаимодействии с сервером.

Например, в приложениях электронной коммерции мутации можно использовать для обработки корзины покупок, обновления профиля пользователя или регистрации на платформе.

1
Задача
Модуль 3: React, 8 уровень, 5 лекция
Недоступна
Простая мутация с использованием useMutation
Простая мутация с использованием useMutation
1
Задача
Модуль 3: React, 8 уровень, 5 лекция
Недоступна
Удаление элемента с подтверждением
Удаление элемента с подтверждением
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ