В TanStack Query мутации используются для изменения данных на
сервере: создания, обновления, удаления, отправки форм, загрузки файлов
и выполнения любых операций с побочными эффектами. Поведение мутаций
можно настраивать через объект параметров useMutation,
однако при большом количестве однотипных мутаций возникает проблема
дублирования конфигурации.
Дефолтные опции мутаций позволяют централизованно задавать общие настройки:
Такая архитектура особенно важна в крупных приложениях, где десятки или сотни мутаций должны вести себя одинаково.
Все дефолтные параметры задаются внутри QueryClient.
import { QueryClient } from '@tanstack/react-query'
const queryClient = new QueryClient({
defaultOptions: {
mutations: {
retry: 1,
retryDelay: 1000,
},
},
})
В данном примере:
Эти настройки применяются ко всем useMutation, если
локальная конфигурация не переопределяет их.
Объект mutations поддерживает большинство параметров,
доступных в useMutation.
const queryClient = new QueryClient({
defaultOptions: {
mutations: {
mutationFn,
gcTime,
networkMode,
retry,
retryDelay,
onError,
onSuccess,
onSettled,
meta,
},
},
})
Основные параметры:
| Параметр | Назначение |
|---|---|
mutationFn |
функция выполнения мутации |
retry |
количество повторов |
retryDelay |
задержка между попытками |
onSuccess |
обработчик успешного выполнения |
onError |
обработчик ошибки |
onSettled |
вызывается всегда |
gcTime |
время хранения мутации |
networkMode |
режим работы сети |
meta |
произвольные метаданные |
TanStack Query использует каскадную систему конфигурации.
Приоритет настроек:
useMutation;setMutationDefaults;defaultOptions.mutations.Пример:
const queryClient = new QueryClient({
defaultOptions: {
mutations: {
retry: 1,
},
},
})
useMutation({
mutationFn: savePost,
retry: 5,
})
Здесь конкретная мутация использует retry: 5, поскольку
локальная настройка имеет более высокий приоритет.
По умолчанию мутации не повторяются автоматически.
Это принципиальное отличие от запросов (queries),
поскольку повторный POST-запрос может привести к дублированию
данных.
Для включения повторов используется retry.
const queryClient = new QueryClient({
defaultOptions: {
mutations: {
retry: 3,
},
},
})
Теперь каждая мутация будет повторяться до трёх раз.
Параметр retry может быть функцией.
const queryClient = new QueryClient({
defaultOptions: {
mutations: {
retry: (failureCount, error) => {
if (error.status === 400) {
return false
}
return failureCount < 2
},
},
},
})
Подобная схема позволяет:
Задержка между повторными попытками может задаваться числом или функцией.
mutations: {
retry: 3,
retryDelay: 1000,
}
mutations: {
retry: 5,
retryDelay: (attempt) => {
return Math.min(1000 * 2 ** attempt, 30000)
},
}
Такой механизм предотвращает агрессивную нагрузку на сервер.
Один из самых популярных сценариев — централизованная обработка ошибок.
const queryClient = new QueryClient({
defaultOptions: {
mutations: {
onError: (error) => {
console.error(error)
},
},
},
})
Подход используется для:
onSuccess позволяет выполнять действия после успешной
мутации.
const queryClient = new QueryClient({
defaultOptions: {
mutations: {
onSuccess: () => {
console.log('Mutation success')
},
},
},
})
Типичные сценарии:
Одно из важнейших применений onSuccess — инвалидировать
связанные query.
const queryClient = new QueryClient({
defaultOptions: {
mutations: {
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: ['posts'],
})
},
},
},
})
После успешной мутации данные списка будут автоматически обновлены.
Инвалидацию можно делать динамической.
mutations: {
onSuccess: (data, variables) => {
queryClient.invalidateQueries({
queryKey: ['post', variables.id],
})
},
}
onSettled вызывается независимо от результата.
mutations: {
onSettled: () => {
console.log('Mutation completed')
},
}
Используется для:
Можно определить глобальную функцию выполнения мутаций.
const queryClient = new QueryClient({
defaultOptions: {
mutations: {
mutationFn: async (variables) => {
const response = await fetch('/api/data', {
method: 'POST',
body: JSON.stringify(variables),
})
return response.json()
},
},
},
})
Теперь useMutation() может использоваться без явного
указания mutationFn.
const mutation = useMutation()
Однако подобная архитектура применяется редко, поскольку разные мутации обычно требуют разных endpoint.
meta позволяет хранить дополнительные данные.
const queryClient = new QueryClient({
defaultOptions: {
mutations: {
meta: {
source: 'web-app',
},
},
},
})
Метаданные доступны внутри lifecycle-обработчиков.
onError: (error, variables, context, mutation) => {
console.log(mutation.meta.source)
}
TanStack Query хранит информацию о мутациях в кеше.
Параметр gcTime определяет время жизни mutation
cache.
mutations: {
gcTime: 1000 * 60 * 10,
}
В данном случае данные мутации будут храниться 10 минут.
Mutation cache содержит:
Это важно для:
Параметр networkMode управляет поведением мутаций при
отсутствии сети.
mutations: {
networkMode: 'online',
}
Мутация выполняется только при наличии подключения.
mutations: {
networkMode: 'always',
}
Мутация выполняется независимо от состояния сети.
mutations: {
networkMode: 'offlineFirst',
}
Поддерживает offline-first архитектуру.
Особенно полезно для:
Распространённый паттерн — единый обработчик уведомлений.
const queryClient = new QueryClient({
defaultOptions: {
mutations: {
onError: (error) => {
toast.error(error.message)
},
onSuccess: () => {
toast.success('Saved')
},
},
},
})
Это устраняет дублирование в компонентах.
Часто требуется единая логика для кодов ответа.
mutations: {
onError: (error) => {
switch (error.status) {
case 401:
logout()
break
case 403:
redirect('/forbidden')
break
case 500:
toast.error('Server error')
break
}
},
}
Дефолтные опции удобно комбинировать с глобальным API-клиентом.
import axios from 'axios'
const api = axios.create({
baseURL: '/api',
})
const queryClient = new QueryClient({
defaultOptions: {
mutations: {
mutationFn: async ({ url, data }) => {
const response = await api.post(url, data)
return response.data
},
},
},
})
Использование:
const mutation = useMutation()
mutation.mutate({
url: '/posts',
data: {
title: 'New post',
},
})
Глобальные настройки подходят не всегда.
Например:
В таких случаях локальная конфигурация переопределяет глобальную.
useMutation({
mutationFn: login,
retry: false,
})
Помимо defaultOptions, TanStack Query поддерживает более
точную настройку через setMutationDefaults.
queryClient.setMutationDefaults(['posts'], {
mutationFn: createPost,
retry: 2,
})
Теперь все мутации с ключом ['posts'] получают эти
настройки.
useMutation({
mutationKey: ['posts'],
})
queryClient.setMutationDefaults(['posts'], {
retry: 2,
})
useMutation({
mutationKey: ['posts'],
retry: 5,
})
Итоговое значение — retry: 5.
В крупных проектах часто используется многоуровневая конфигурация:
defaultOptions: {
mutations: {
retry: 1,
networkMode: 'online',
},
}
queryClient.setMutationDefaults(['users'], {
retry: 3,
})
useMutation({
mutationKey: ['users'],
retry: false,
})
Такой подход создаёт предсказуемую систему поведения.
Повторная отправка может создать дубликаты.
Особенно опасно для:
Ошибка:
onSuccess: () => {
queryClient.invalidateQueries()
}
Это приводит к массовым лишним запросам.
onSuccess: async () => {
await hugeOperation()
}
Подобные операции замедляют mutation lifecycle.
Разные endpoints часто требуют:
Избыточная универсализация усложняет код.
import { QueryClient } from '@tanstack/react-query'
export const queryClient = new QueryClient({
defaultOptions: {
mutations: {
retry: (count, error) => {
if (error.status >= 400 && error.status < 500) {
return false
}
return count < 2
},
retryDelay: (attempt) => {
return Math.min(1000 * 2 ** attempt, 10000)
},
networkMode: 'online',
onError: (error) => {
console.error(error)
toast.error(error.message)
},
gcTime: 1000 * 60 * 5,
},
},
})
Глобальные callbacks могут использоваться для optimistic UI.
mutations: {
onError: (error, variables, context) => {
if (context?.rollback) {
context.rollback()
}
},
}
Mutation defaults отображаются в React Query Devtools.
Это помогает:
Подход особенно полезен при наличии:
Избыточная глобализация создаёт проблемы:
Чем критичнее мутация, тем более явной должна быть её локальная конфигурация.