В библиотеке TanStack Query параметры onSuccess,
onError и onSettled используются для обработки
побочных эффектов после выполнения запроса или мутации. Эти параметры
позволяют запускать дополнительную логику в ответ на успешное получение
данных, возникновение ошибки или завершение операции вне зависимости от
результата.
Они применяются как в useQuery, так и в
useMutation, однако наиболее активно используются именно в
мутациях, поскольку операции изменения данных почти всегда
сопровождаются дополнительными действиями:
onSuccessonSuccess вызывается только после успешного выполнения
запроса или мутации.
Если сервер вернул корректный результат и операция завершилась без
ошибки, выполняется функция, переданная в onSuccess.
useMutation({
mutationFn: createPost,
onSuccess: (data) => {
console.log('Успешно:', data);
}
});
onSuccessВ зависимости от типа хука аргументы могут отличаться.
useQueryonSuccess: (data) => {}
Доступен результат запроса.
useMutationonSuccess: (data, variables, context) => {}
Аргументы:
| Аргумент | Описание |
|---|---|
data |
Ответ сервера |
variables |
Данные, переданные в mutate |
context |
Контекст из onMutate |
const mutation = useMutation({
mutationFn: createUser,
onSuccess: (data) => {
console.log('Пользователь создан');
console.log(data);
}
});
variablesvariables содержат аргументы, переданные в мутацию.
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('Настройки сохранены');
}
});
onErroronError вызывается только при возникновении ошибки.
Это основной механизм централизованной обработки ошибок в
TanStack Query.
useMutation({
mutationFn: savePost,
onError: (error) => {
console.error(error);
}
});
onErroruseQueryonError: (error) => {}
useMutationonError: (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-клиенты возвращают структуру ошибки.
Пример с axios:
onError: (error) => {
console.log(error.response.status);
console.log(error.response.data);
}
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
);
}
});
Optimistic update обновляет интерфейс до ответа сервера. Если сервер вернул ошибку, интерфейс необходимо вернуть в исходное состояние.
Без rollback пользователь увидит некорректные данные.
onSettledonSettled вызывается всегда:
Это аналог конструкции finally из
Promise.
useMutation({
mutationFn: saveData,
onSettled: () => {
console.log('Операция завершена');
}
});
onSettleduseQueryonSettled: (data, error) => {}
useMutationonSettled: (
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-параметры могут быть асинхронными.
useMutation({
mutationFn: saveArticle,
onSuccess: async () => {
await analytics.track('article_saved');
}
});
mutateCallbacks можно передавать не только в конфигурации хука, но и
непосредственно в mutate.
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 в
useQueryuseQuery({
queryKey: ['profile'],
queryFn: fetchProfile,
onSuccess: (data) => {
console.log('Профиль загружен');
}
});
onError в
useQueryuseQuery({
queryKey: ['profile'],
queryFn: fetchProfile,
onError: (error) => {
console.log(error.message);
}
});
onSettled в
useQueryuseQuery({
queryKey: ['profile'],
queryFn: fetchProfile,
onSettled: () => {
console.log('Запрос завершён');
}
});
onSuccess без необходимостиonSuccess: (data) => {
setUser(data);
}
В большинстве случаев это лишнее, поскольку данные уже находятся в
кэше TanStack Query.
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: ['todos']
});
}
onSettled: () => {
queryClient.invalidateQueries({
queryKey: ['todos']
});
}
Повторное инвалидирование бессмысленно.
Callbacks не должны содержать сложную бизнес-логику или тяжёлые вычисления.
Нежелательно:
onSuccess: (data) => {
const hugeResult = expensiveCalculation(data);
}
Если onMutate ничего не возвращает:
onMutate: () => {}
то в onError параметр context будет
undefined.
onSuccessПодходит для:
onErrorПодходит для:
onSettledПодходит для:
finally.