React 19: useOptimistic с React Query для оптимистичных мутаций и rollback
-

В React 19 появился хук useOptimistic, который упрощает создание оптимистичных обновлений интерфейса. Он позволяет сразу показывать изменения пользователю, не дожидаясь ответа сервера, и автоматически откатывать их при ошибках. Это делает приложения отзывчивыми, особенно при мутациях вроде добавления комментариев или лайков.
С React Query этот хук сочетается идеально для управления кэшем. Мы получим быструю обратную связь, минимизируем задержки и сохраним данные в актуальном состоянии. Проблемы с долгим ожиданием запросов уйдут, а UI станет плавным и предсказуемым.
Что такое useOptimistic и зачем он нужен
Хук useOptimistic принимает текущее состояние и функцию-обновлятор, возвращая оптимистичное состояние для рендера. Пока асинхронная операция выполняется, пользователь видит предполагаемый результат - например, новый элемент в списке. Если сервер подтвердит изменения, состояние синхронизируется естественно. При ошибке оптимистичное состояние откатывается автоматически.
Это решает проблему медленных сетевых запросов. Вместо пустого экрана или спиннера UI реагирует мгновенно. В React 19 хук работает с формами и действиями, интегрируясь с useTransition для фоновых обновлений. Реальный пример - чат: сообщение появляется сразу с меткой “отправка”, а после успеха статус убирается.
- Параметры хука: Первое - исходное состояние (state), второе - чистая функция updateFn(currentState, optimisticValue), возвращающая новое состояние.
- Добавление оптимистичных изменений: Используйте возвращенную функцию addOptimistic для предварительного обновления перед запросом.
- Автоматический rollback: При ошибке в асинхронной операции состояние возвращается к исходному без лишнего кода.
- Синхронизация: После успеха обновите реальное состояние - оптимистичное подхватит изменения.
Свойство Описание Пример state Исходное состояние Массив сообщений [] updateFn Функция обновления (state, newMsg) => […state, {text: newMsg, sending: true}] addOptimistic Setter для оптимизма addOptimistic(‘Привет’) Интеграция useOptimistic с React Query
React Query управляет кэшем запросов и мутациями, а useOptimistic добавляет слой мгновенных обновлений UI. Вместо ручного setQueryData мы используем хук для локального состояния, параллельно отправляя мутацию. Ключ - комбинировать onMutate для предварительных изменений в кэше с хуком React 19.
При мутации вроде добавления todo: сначала optimistic показывает новый элемент, React Query обновляет кэш через queryClient.setQueryData. Если ошибка, rollback восстанавливает предыдущие данные. Это работает с несколькими queryKey - обновляем кэш в разных местах атомарно. Такой подход снижает boilerplate по сравнению с чистым React Query.
- useMutation с onMutate: Внутри onMutate вызывайте optimistic функции для нескольких queryKey, сохраняйте previousData для rollback.
- queryClient.setQueryData: Применяйте updater к кэшу по queryKey перед реальным запросом.
- onError rollback: Восстанавливайте previousData, invalidateQueries для свежих данных.
Шаг Действие в React Query Связь с useOptimistic 1 onMutate: optimistic() addOptimistic для UI 2 setQueryData(updater) Синхронизирует кэш 3 mutationFn() Реальный API-запрос 4 onError: rollback() Откат optimistic состояния Пример: оптимистичная мутация todo-списка
Представьте список задач с мутацией добавления. Пользователь вводит текст, нажимает кнопку - задача появляется сразу вверху с индикатором “сохранение”. React Query отправляет POST-запрос, обновляет кэш. При успехе индикатор исчезает, при ошибке - задача удаляется из списка.
Код использует useOptimistic для рендера, useMutation для логики. В onMutate сохраняем previousTodo, применяем updater. Это масштабируется на like/unlike, где несколько запросов обновляются одновременно. Нюанс: используйте exact: true в invalidateQueries, чтобы не сбросить весь кэш.
const [optimisticTodos, addOptimisticTodo] = useOptimistic(todos, (state, newTodo) => [ { ...newTodo, isPending: true }, ...state ]); const mutation = useMutation({ mutationFn: createTodo, onMutate: async (newTodo) => { addOptimisticTodo(newTodo); const previousTodos = queryClient.getQueryData(['todos']); queryClient.setQueryData(['todos'], (old) => [...old, newTodo]); return { previousTodos }; }, onError: (err, newTodo, context) => { queryClient.setQueryData(['todos'], context.previousTodos); } });- Масштабирование: Для нескольких queryKey возвращайте массив handlers с queryKey и updater.
- Фильтры: Поддержка setQueriesData с predicate для точечных обновлений.
- Кастомные хуки: Оберните в useOptimisticMutation для переиспользования.
Готовые рецепты для сложных случаев
Сценарий Реализация Преимущества Чат с доставкой optimistic + deliverMessage Мгновенные сообщения, rollback на ошибке Корзина покупок updateFn для добавления items Плавный шопинг без лагов Лайки/дизлайки Множественные queryKey Синхронные обновления счетчиков Важно: updateFn должна быть чистой, без сайд-эффектов. Это гарантирует предсказуемость.
Практические нюансы комбинации хуков
Масштабирование на реальные проекты
Комбинация дает отзывчивость без потери надежности. useOptimistic управляет UI-состоянием, React Query - данными и кэшем. В больших приложениях добавьте типизацию TypeScript для TOptimisticDataArray. Тестируйте rollback на слабом соединении.
Остается пространство для экспериментов: интегрируйте с React Server Components или TanStack Query v5. Поддержка фильтров в setQueriesData упростит обновления списков. Стоит изучить кастомные onError для уведомлений об ошибках.
Здравствуйте! Похоже, вас заинтересовала эта беседа, но у вас ещё нет аккаунта.
Надоело каждый раз пролистывать одни и те же посты? Зарегистрировав аккаунт, вы всегда будете возвращаться на ту же страницу, где были раньше, и сможете выбирать, получать ли уведомления о новых ответах (по электронной почте или в виде push-уведомлений). Вы также сможете сохранять закладки и ставить лайки постам, чтобы выразить свою благодарность другим участникам сообщества.
С вашими комментариями этот пост мог бы стать ещё лучше 💗
Зарегистрироваться Войти© 2024 - 2026 ExLends, Inc. Все права защищены.