Параметры onSuccess, onError, onSettled

В библиотеке TanStack Query параметры onSuccess, onError и onSettled используются для обработки побочных эффектов после выполнения запроса или мутации. Эти параметры позволяют запускать дополнительную логику в ответ на успешное получение данных, возникновение ошибки или завершение операции вне зависимости от результата.

Они применяются как в useQuery, так и в useMutation, однако наиболее активно используются именно в мутациях, поскольку операции изменения данных почти всегда сопровождаются дополнительными действиями:

  • показ уведомлений;
  • перенаправление пользователя;
  • инвалидирование кэша;
  • логирование;
  • синхронизация состояния;
  • аналитика;
  • обновление интерфейса.

Параметр onSuccess

Основная задача

onSuccess вызывается только после успешного выполнения запроса или мутации.

Если сервер вернул корректный результат и операция завершилась без ошибки, выполняется функция, переданная в onSuccess.


Синтаксис

useMutation({
    mutationFn: createPost,
    onSuccess: (data) => {
        console.log('Успешно:', data);
    }
});

Аргументы onSuccess

В зависимости от типа хука аргументы могут отличаться.

Для useQuery

onSuccess: (data) => {}

Доступен результат запроса.


Для useMutation

onSuccess: (data, variables, context) => {}

Аргументы:

Аргумент Описание
data Ответ сервера
variables Данные, переданные в mutate
context Контекст из onMutate

Пример успешной мутации

const mutation = useMutation({
    mutationFn: createUser,

    onSuccess: (data) => {
        console.log('Пользователь создан');
        console.log(data);
    }
});

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

variables содержат аргументы, переданные в мутацию.

const mutation = useMutation({
    mutationFn: updateUser,

    onSuccess: (data, variables) => {
        console.log('ID пользователя:', variables.id);
    }
});

mutation.mutate({
    id: 15,
    name: 'Alex'
});

Обновление кэша после успешной мутации

Одно из наиболее важных применений onSuccess — синхронизация кэша.

Инвалидирование запросов

const queryClient = useQueryClient();

const mutation = useMutation({
    mutationFn: createTodo,

    onSuccess: () => {
        queryClient.invalidateQueries({
            queryKey: ['todos']
        });
    }
});

После успешного создания записи список todos будет автоматически перезапрошен.


Прямое обновление кэша

Иногда повторный запрос не нужен.

const queryClient = useQueryClient();

const mutation = useMutation({
    mutationFn: updateTodo,

    onSuccess: (updatedTodo) => {
        queryClient.setQueryData(
            ['todo', updatedTodo.id],
            updatedTodo
        );
    }
});

Такой подход уменьшает количество сетевых запросов.


Переход между страницами

onSuccess часто используется вместе с роутингом.

const navigate = useNavigate();

const mutation = useMutation({
    mutationFn: login,

    onSuccess: () => {
        navigate('/dashboard');
    }
});

Отображение уведомлений

const mutation = useMutation({
    mutationFn: saveSettings,

    onSuccess: () => {
        toast.success('Настройки сохранены');
    }
});

Параметр onError

Основная задача

onError вызывается только при возникновении ошибки.

Это основной механизм централизованной обработки ошибок в TanStack Query.


Синтаксис

useMutation({
    mutationFn: savePost,

    onError: (error) => {
        console.error(error);
    }
});

Аргументы onError

Для useQuery

onError: (error) => {}

Для useMutation

onError: (error, variables, context) => {}
Аргумент Описание
error Объект ошибки
variables Переданные параметры
context Контекст из onMutate

Пример обработки ошибки

const mutation = useMutation({
    mutationFn: createPost,

    onError: (error) => {
        console.log(error.message);
    }
});

Показ уведомлений об ошибках

const mutation = useMutation({
    mutationFn: updateProfile,

    onError: () => {
        toast.error('Ошибка обновления профиля');
    }
});

Обработка HTTP-ошибок

Многие HTTP-клиенты возвращают структуру ошибки.

Пример с axios:

onError: (error) => {
    console.log(error.response.status);
    console.log(error.response.data);
}

Откат optimistic update

onError тесно связан с onMutate.

Полный пример

const queryClient = useQueryClient();

const mutation = useMutation({
    mutationFn: updateTodo,

    onMutate: async (newTodo) => {
        await queryClient.cancelQueries({
            queryKey: ['todos']
        });

        const previousTodos = queryClient.getQueryData(['todos']);

        queryClient.setQueryData(['todos'], (old) => {
            return old.map((todo) =>
                todo.id === newTodo.id
                    ? { ...todo, ...newTodo }
                    : todo
            );
        });

        return { previousTodos };
    },

    onError: (error, newTodo, context) => {
        queryClient.setQueryData(
            ['todos'],
            context.previousTodos
        );
    }
});

Почему rollback важен

Optimistic update обновляет интерфейс до ответа сервера. Если сервер вернул ошибку, интерфейс необходимо вернуть в исходное состояние.

Без rollback пользователь увидит некорректные данные.


Параметр onSettled

Основная задача

onSettled вызывается всегда:

  • после успешного выполнения;
  • после ошибки.

Это аналог конструкции finally из Promise.


Синтаксис

useMutation({
    mutationFn: saveData,

    onSettled: () => {
        console.log('Операция завершена');
    }
});

Аргументы onSettled

Для useQuery

onSettled: (data, error) => {}

Для useMutation

onSettled: (
    data,
    error,
    variables,
    context
) => {}
Аргумент Описание
data Результат при успехе
error Ошибка при неудаче
variables Параметры мутации
context Контекст

Пример использования

const mutation = useMutation({
    mutationFn: uploadFile,

    onSettled: () => {
        console.log('Загрузка завершена');
    }
});

Очистка состояния

const mutation = useMutation({
    mutationFn: sendMessage,

    onSettled: () => {
        setLoading(false);
    }
});

Инвалидирование независимо от результата

Иногда обновление данных необходимо даже после ошибки.

const queryClient = useQueryClient();

const mutation = useMutation({
    mutationFn: deletePost,

    onSettled: () => {
        queryClient.invalidateQueries({
            queryKey: ['posts']
        });
    }
});

Сравнение onSuccess, onError, onSettled

Параметр Когда вызывается
onSuccess Только при успехе
onError Только при ошибке
onSettled Всегда

Последовательность вызовов

Успешная мутация

mutationFn
↓
onSuccess
↓
onSettled

Ошибка мутации

mutationFn
↓
onError
↓
onSettled

Асинхронные callback-функции

Все callback-параметры могут быть асинхронными.

useMutation({
    mutationFn: saveArticle,

    onSuccess: async () => {
        await analytics.track('article_saved');
    }
});

Использование вместе с mutate

Callbacks можно передавать не только в конфигурации хука, но и непосредственно в mutate.


Локальные callbacks

mutation.mutate(data, {
    onSuccess: () => {
        console.log('Локальный success');
    }
});

Приоритет выполнения

Сначала выполняются callbacks из useMutation, затем локальные callbacks из mutate.


Пример полного жизненного цикла мутации

const queryClient = useQueryClient();

const mutation = useMutation({
    mutationFn: updateUser,

    onMutate: async (newUser) => {
        await queryClient.cancelQueries({
            queryKey: ['user', newUser.id]
        });

        const previousUser =
            queryClient.getQueryData([
                'user',
                newUser.id
            ]);

        queryClient.setQueryData(
            ['user', newUser.id],
            (old) => ({
                ...old,
                ...newUser
            })
        );

        return { previousUser };
    },

    onSuccess: (data) => {
        toast.success('Профиль обновлён');
    },

    onError: (error, variables, context) => {
        queryClient.setQueryData(
            ['user', variables.id],
            context.previousUser
        );

        toast.error('Ошибка обновления');
    },

    onSettled: (data, error, variables) => {
        queryClient.invalidateQueries({
            queryKey: ['user', variables.id]
        });
    }
});

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

Хотя callbacks особенно популярны в мутациях, они доступны и в запросах.


Пример onSuccess в useQuery

useQuery({
    queryKey: ['profile'],
    queryFn: fetchProfile,

    onSuccess: (data) => {
        console.log('Профиль загружен');
    }
});

Пример onError в useQuery

useQuery({
    queryKey: ['profile'],
    queryFn: fetchProfile,

    onError: (error) => {
        console.log(error.message);
    }
});

Пример onSettled в useQuery

useQuery({
    queryKey: ['profile'],
    queryFn: fetchProfile,

    onSettled: () => {
        console.log('Запрос завершён');
    }
});

Типичные ошибки при использовании callbacks

Изменение состояния внутри onSuccess без необходимости

onSuccess: (data) => {
    setUser(data);
}

В большинстве случаев это лишнее, поскольку данные уже находятся в кэше TanStack Query.


Дублирование логики

onSuccess: () => {
    queryClient.invalidateQueries({
        queryKey: ['todos']
    });
}

onSettled: () => {
    queryClient.invalidateQueries({
        queryKey: ['todos']
    });
}

Повторное инвалидирование бессмысленно.


Тяжёлые вычисления внутри callbacks

Callbacks не должны содержать сложную бизнес-логику или тяжёлые вычисления.

Нежелательно:

onSuccess: (data) => {
    const hugeResult = expensiveCalculation(data);
}

Ошибки rollback

Если onMutate ничего не возвращает:

onMutate: () => {}

то в onError параметр context будет undefined.


Когда использовать onSuccess

Подходит для:

  • уведомлений;
  • навигации;
  • обновления кэша;
  • запуска аналитики;
  • синхронизации UI;
  • цепочек запросов.

Когда использовать onError

Подходит для:

  • отображения ошибок;
  • rollback optimistic update;
  • логирования;
  • обработки сетевых проблем;
  • обработки серверных ошибок.

Когда использовать onSettled

Подходит для:

  • очистки состояния;
  • скрытия loader;
  • завершения процесса;
  • инвалидирования вне зависимости от результата;
  • логики наподобие finally.