Optimistic UI — это подход к построению пользовательского интерфейса, при котором изменения в интерфейсе отображаются сразу, ещё до подтверждения операции сервером. Такой подход создаёт ощущение мгновенной реакции приложения, улучшая пользовательский опыт.
В контексте React Router ключевую роль в реализации
Optimistic UI играет хук useFetcher. Он позволяет
отправлять запросы на сервер асинхронно и обновлять состояние интерфейса
без полной перезагрузки страницы или перехода по маршруту.
useFetcheruseFetcher предоставляет объект, через который можно
отправлять действия (action) и запросы
(loader) для конкретного маршрута, независимо от текущего
маршрута. Основные свойства и методы, которые он предоставляет:
fetcher.submit(data, options) — отправка данных на
сервер. data может быть объектом FormData
или простым объектом, а options определяют метод
(GET/POST) и путь маршрута.fetcher.load(url) — загрузка данных с указанного
маршрута без навигации.fetcher.data — данные, возвращённые сервером.fetcher.state — текущее состояние запроса:
"idle", "submitting",
"loading".fetcher.formMethod, fetcher.formAction —
свойства, доступные при использовании формы.fetcher.submit.import { useFetcher } from "react-router-dom";
import { useState } from "react";
function CommentForm({ postId }) {
const fetcher = useFetcher();
const [optimisticComments, setOptimisticComments] = useState([]);
const handleSubmit = (e) => {
e.preventDefault();
const formData = new FormData(e.target);
const commentText = formData.get("comment");
// Добавление оптимистичного комментария
const tempComment = {
id: `temp-${Date.now()}`,
text: commentText,
pending: true
};
setOptimisticComments([...optimisticComments, tempComment]);
// Отправка на сервер
fetcher.submit(formData, { method: "post", action: `/posts/${postId}/comments` });
// Очистка формы
e.target.reset();
};
// Обновление списка после ответа сервера
const comments = fetcher.data?.comments ?? [];
const combinedComments = [...optimisticComments.filter(c => c.pending), ...comments];
return (
<fetcher.Form onSub mit={handleSubmit}>
<input name="comment" type="text" required />
<button type="submit" disabled={fetcher.state === "submitting"}>Добавить</button>
<ul>
{combinedComments.map(c => (
<li key={c.id}>{c.text} {c.pending && "(отправляется…)"}</li>
))}
</ul>
</fetcher.Form>
);
}
Разбор ключевых моментов:
optimisticComments хранит локальные изменения до
подтверждения сервера.pending.fetcher.submit выполняет POST-запрос на сервер.fetcher.stateХук useFetcher автоматически обновляет состояние:
"idle" — нет активного запроса."submitting" — данные отправляются на сервер."loading" — загрузка данных с маршрута или обновление
после действия.Для Optimistic UI важно реагировать на "submitting" для
отображения индикаторов и предотвращения повторных запросов.
<button type="submit" disabled={fetcher.state === "submitting"}>
{fetcher.state === "submitting" ? "Отправка..." : "Добавить"}
</button>
Если сервер вернёт ошибку, оптимистичный комментарий следует удалить или отметить ошибкой:
useEffect(() => {
if (fetcher.data?.error) {
setOptimisticComments(prev =>
prev.map(c => c.pending ? { ...c, error: fetcher.data.error } : c)
);
} else {
// Удаляем флаг pending для подтверждённых комментариев
setOptimisticComments(prev =>
prev.map(c => ({ ...c, pending: false }))
);
}
}, [fetcher.data]);
temp-${Date.now()}) позволяют корректно обновлять и
удалять элементы после ответа.fetcher.load для обновления
данных: позволяет подтягивать актуальные данные после действий
без навигации.useFetcher в связке с Optimistic UI позволяет создавать
интерфейсы, которые реагируют мгновенно, не блокируют пользователя и
поддерживают согласованность с сервером. Такой подход особенно
эффективен для форм, списков комментариев, лайков и других интерактивных
элементов, где важна скорость отклика.