Дефолтные опции мутаций

В TanStack Query мутации используются для изменения данных на сервере: создания, обновления, удаления, отправки форм, загрузки файлов и выполнения любых операций с побочными эффектами. Поведение мутаций можно настраивать через объект параметров useMutation, однако при большом количестве однотипных мутаций возникает проблема дублирования конфигурации.

Дефолтные опции мутаций позволяют централизованно задавать общие настройки:

  • обработку ошибок;
  • автоматические повторные попытки;
  • поведение optimistic updates;
  • глобальные колбэки;
  • стратегию инвалидации;
  • retry delay;
  • сетевое поведение;
  • meta-информацию;
  • mutationFn по умолчанию.

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


Глобальная настройка мутаций через QueryClient

Все дефолтные параметры задаются внутри QueryClient.

import { QueryClient } from '@tanstack/react-query'

const queryClient = new QueryClient({
    defaultOptions: {
        mutations: {
            retry: 1,
            retryDelay: 1000,
        },
    },
})

В данном примере:

  • каждая мутация будет автоматически повторяться один раз;
  • задержка между попытками составит 1000 мс.

Эти настройки применяются ко всем useMutation, если локальная конфигурация не переопределяет их.


Структура defaultOptions.mutations

Объект 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 использует каскадную систему конфигурации.

Приоритет настроек:

  1. параметры внутри useMutation;
  2. setMutationDefaults;
  3. defaultOptions.mutations.

Пример:

const queryClient = new QueryClient({
    defaultOptions: {
        mutations: {
            retry: 1,
        },
    },
})

useMutation({
    mutationFn: savePost,
    retry: 5,
})

Здесь конкретная мутация использует retry: 5, поскольку локальная настройка имеет более высокий приоритет.


Retry для мутаций

По умолчанию мутации не повторяются автоматически.

Это принципиальное отличие от запросов (queries), поскольку повторный POST-запрос может привести к дублированию данных.

Для включения повторов используется retry.

const queryClient = new QueryClient({
    defaultOptions: {
        mutations: {
            retry: 3,
        },
    },
})

Теперь каждая мутация будет повторяться до трёх раз.


Retry как функция

Параметр retry может быть функцией.

const queryClient = new QueryClient({
    defaultOptions: {
        mutations: {
            retry: (failureCount, error) => {
                if (error.status === 400) {
                    return false
                }

                return failureCount < 2
            },
        },
    },
})

Подобная схема позволяет:

  • не повторять ошибки валидации;
  • повторять сетевые ошибки;
  • ограничивать retries для отдельных HTTP-статусов.

Настройка retryDelay

Задержка между повторными попытками может задаваться числом или функцией.

Статическая задержка

mutations: {
    retry: 3,
    retryDelay: 1000,
}

Экспоненциальная задержка

mutations: {
    retry: 5,
    retryDelay: (attempt) => {
        return Math.min(1000 * 2 ** attempt, 30000)
    },
}

Такой механизм предотвращает агрессивную нагрузку на сервер.


Глобальный onError

Один из самых популярных сценариев — централизованная обработка ошибок.

const queryClient = new QueryClient({
    defaultOptions: {
        mutations: {
            onError: (error) => {
                console.error(error)
            },
        },
    },
})

Подход используется для:

  • отправки ошибок в Sentry;
  • логирования;
  • отображения toast-уведомлений;
  • централизованной аналитики.

Глобальный onSuccess

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

const queryClient = new QueryClient({
    defaultOptions: {
        mutations: {
            onSuccess: () => {
                console.log('Mutation success')
            },
        },
    },
})

Типичные сценарии:

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

Автоматическая инвалидация запросов

Одно из важнейших применений onSuccess — инвалидировать связанные query.

const queryClient = new QueryClient({
    defaultOptions: {
        mutations: {
            onSuccess: () => {
                queryClient.invalidateQueries({
                    queryKey: ['posts'],
                })
            },
        },
    },
})

После успешной мутации данные списка будут автоматически обновлены.


Условная инвалидация

Инвалидацию можно делать динамической.

mutations: {
    onSuccess: (data, variables) => {
        queryClient.invalidateQueries({
            queryKey: ['post', variables.id],
        })
    },
}

Глобальный onSettled

onSettled вызывается независимо от результата.

mutations: {
    onSettled: () => {
        console.log('Mutation completed')
    },
}

Используется для:

  • остановки лоадеров;
  • очистки временных данных;
  • закрытия modal window;
  • снятия блокировок интерфейса.

Использование mutationFn по умолчанию

Можно определить глобальную функцию выполнения мутаций.

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

meta позволяет хранить дополнительные данные.

const queryClient = new QueryClient({
    defaultOptions: {
        mutations: {
            meta: {
                source: 'web-app',
            },
        },
    },
})

Метаданные доступны внутри lifecycle-обработчиков.

onError: (error, variables, context, mutation) => {
    console.log(mutation.meta.source)
}

gcTime для мутаций

TanStack Query хранит информацию о мутациях в кеше.

Параметр gcTime определяет время жизни mutation cache.

mutations: {
    gcTime: 1000 * 60 * 10,
}

В данном случае данные мутации будут храниться 10 минут.


Особенности mutation cache

Mutation cache содержит:

  • статус;
  • ошибки;
  • variables;
  • response;
  • metadata;
  • timestamps.

Это важно для:

  • devtools;
  • offline mode;
  • повторного воспроизведения;
  • persisted cache.

Network Mode

Параметр networkMode управляет поведением мутаций при отсутствии сети.

online

mutations: {
    networkMode: 'online',
}

Мутация выполняется только при наличии подключения.


always

mutations: {
    networkMode: 'always',
}

Мутация выполняется независимо от состояния сети.


offlineFirst

mutations: {
    networkMode: 'offlineFirst',
}

Поддерживает offline-first архитектуру.

Особенно полезно для:

  • PWA;
  • мобильных приложений;
  • синхронизации данных;
  • очередей операций.

Централизованный toast handler

Распространённый паттерн — единый обработчик уведомлений.

const queryClient = new QueryClient({
    defaultOptions: {
        mutations: {
            onError: (error) => {
                toast.error(error.message)
            },

            onSuccess: () => {
                toast.success('Saved')
            },
        },
    },
})

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


Глобальная обработка HTTP-ошибок

Часто требуется единая логика для кодов ответа.

mutations: {
    onError: (error) => {
        switch (error.status) {
            case 401:
                logout()
                break

            case 403:
                redirect('/forbidden')
                break

            case 500:
                toast.error('Server error')
                break
        }
    },
}

Интеграция с Axios

Дефолтные опции удобно комбинировать с глобальным 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',
    },
})

Разделение стратегий для разных мутаций

Глобальные настройки подходят не всегда.

Например:

  • формы авторизации нельзя повторять;
  • upload-файлы нельзя автоматически переотправлять;
  • аналитические события допустимо повторять.

В таких случаях локальная конфигурация переопределяет глобальную.

useMutation({
    mutationFn: login,
    retry: false,
})

setMutationDefaults

Помимо defaultOptions, TanStack Query поддерживает более точную настройку через setMutationDefaults.

queryClient.setMutationDefaults(['posts'], {
    mutationFn: createPost,
    retry: 2,
})

Теперь все мутации с ключом ['posts'] получают эти настройки.


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

useMutation({
    mutationKey: ['posts'],
})

Локальные настройки поверх defaults

queryClient.setMutationDefaults(['posts'], {
    retry: 2,
})

useMutation({
    mutationKey: ['posts'],
    retry: 5,
})

Итоговое значение — retry: 5.


Архитектура enterprise-приложений

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

Глобальный слой

defaultOptions: {
    mutations: {
        retry: 1,
        networkMode: 'online',
    },
}

Domain defaults

queryClient.setMutationDefaults(['users'], {
    retry: 3,
})

Локальная конфигурация

useMutation({
    mutationKey: ['users'],
    retry: false,
})

Такой подход создаёт предсказуемую систему поведения.


Частые ошибки

Автоматический retry для POST-запросов

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

Особенно опасно для:

  • платежей;
  • заказов;
  • регистраций;
  • финансовых операций.

Глобальная инвалидация всех queries

Ошибка:

onSuccess: () => {
    queryClient.invalidateQueries()
}

Это приводит к массовым лишним запросам.


Тяжёлые операции внутри onSuccess

onSuccess: async () => {
    await hugeOperation()
}

Подобные операции замедляют mutation lifecycle.


Использование mutationFn по умолчанию для несовместимых API

Разные endpoints часто требуют:

  • разные методы;
  • headers;
  • payload;
  • сериализацию;
  • multipart/form-data.

Избыточная универсализация усложняет код.


Практический production-пример

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

Связь дефолтных опций с optimistic updates

Глобальные callbacks могут использоваться для optimistic UI.

mutations: {
    onError: (error, variables, context) => {
        if (context?.rollback) {
            context.rollback()
        }
    },
}

Взаимодействие с Devtools

Mutation defaults отображаются в React Query Devtools.

Это помогает:

  • анализировать retries;
  • отслеживать mutation state;
  • смотреть cache lifecycle;
  • проверять invalidation;
  • изучать network behavior.

Когда использовать defaultOptions.mutations

Подход особенно полезен при наличии:

  • единой политики retry;
  • общего error handling;
  • общей toast-системы;
  • централизованной аналитики;
  • глобального API-клиента;
  • offline architecture;
  • shared mutation lifecycle.

Когда не стоит злоупотреблять глобальными настройками

Избыточная глобализация создаёт проблемы:

  • скрытое поведение;
  • сложность дебага;
  • неявные side effects;
  • непредсказуемые retries;
  • каскадные invalidation.

Чем критичнее мутация, тем более явной должна быть её локальная конфигурация.