Перейти к содержимому

9. Query: Optimistic Updates

Оптимистичное обновление — техника UX, при которой интерфейс мгновенно отражает изменение, не дожидаясь ответа сервера. Если сервер вернёт ошибку — UI откатывается к предыдущему состоянию.

Это создаёт ощущение мгновенного отклика, особенно заметное при лайках, завершении задач, изменении имени.

Оптимистичные обновления используют три коллбэка useMutation:

  1. onMutate — выполняется до запроса. Обновляем кэш, сохраняем предыдущее состояние
  2. onError — выполняется при ошибке. Откатываем кэш к сохранённому состоянию
  3. onSettled — выполняется всегда. Инвалидируем кэш для синхронизации с сервером
const queryClient = useQueryClient()
const toggleLike = useMutation({
mutationFn: (postId: number) => likePost(postId),
onMutate: async (postId) => {
// 1. Отменяем текущие запросы (избегаем перезаписи нашего оптимистичного обновления)
await queryClient.cancelQueries({ queryKey: ['posts'] })
// 2. Сохраняем предыдущее состояние для отката
const previousPosts = queryClient.getQueryData<Post[]>(['posts'])
// 3. Оптимистично обновляем кэш
queryClient.setQueryData<Post[]>(['posts'], (old) =>
old?.map(post =>
post.id === postId
? { ...post, liked: !post.liked, likes: post.liked ? post.likes - 1 : post.likes + 1 }
: post
)
)
// 4. Возвращаем контекст для onError
return { previousPosts }
},
onError: (err, postId, context) => {
// Откат при ошибке
if (context?.previousPosts) {
queryClient.setQueryData(['posts'], context.previousPosts)
}
},
onSettled: () => {
// Синхронизация с сервером после завершения
queryClient.invalidateQueries({ queryKey: ['posts'] })
},
})

Без отмены запросов возможна гонка: идёт фоновый запрос ['posts'], мы оптимистично обновляем кэш, но когда фоновый запрос завершается — он перезапишет наши изменения.

onMutate может вернуть любое значение — оно передаётся как context в onError и onSettled. Используйте это для сохранения предыдущего состояния.

useMutation<
ResponseType, // тип ответа сервера
ErrorType, // тип ошибки
VariablesType, // тип аргументов mutate()
ContextType // тип возвращаемого onMutate
>({ ... })