9. Query: Optimistic Updates
Что такое оптимистичные обновления?
Заголовок раздела «Что такое оптимистичные обновления?»Оптимистичное обновление — техника UX, при которой интерфейс мгновенно отражает изменение, не дожидаясь ответа сервера. Если сервер вернёт ошибку — UI откатывается к предыдущему состоянию.
Это создаёт ощущение мгновенного отклика, особенно заметное при лайках, завершении задач, изменении имени.
Паттерн реализации
Заголовок раздела «Паттерн реализации»Оптимистичные обновления используют три коллбэка useMutation:
onMutate— выполняется до запроса. Обновляем кэш, сохраняем предыдущее состояниеonError— выполняется при ошибке. Откатываем кэш к сохранённому состоянию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'] }) },})Ключевые детали
Заголовок раздела «Ключевые детали»Почему cancelQueries?
Заголовок раздела «Почему cancelQueries?»Без отмены запросов возможна гонка: идёт фоновый запрос ['posts'], мы оптимистично обновляем кэш,
но когда фоновый запрос завершается — он перезапишет наши изменения.
Context в onError
Заголовок раздела «Context в onError»onMutate может вернуть любое значение — оно передаётся как context в onError и onSettled.
Используйте это для сохранения предыдущего состояния.
Типизация
Заголовок раздела «Типизация»useMutation< ResponseType, // тип ответа сервера ErrorType, // тип ошибки VariablesType, // тип аргументов mutate() ContextType // тип возвращаемого onMutate>({ ... })