12. Optimistic UI
Optimistic UI — техника, при которой интерфейс обновляется мгновенно, не дожидаясь ответа сервера. Remix делает это простым с помощью useFetcher и useOptimistic.
useFetcher для Optimistic Updates
Заголовок раздела «useFetcher для Optimistic Updates»import { useFetcher } from "@remix-run/react";
function TodoItem({ todo }: { todo: Todo }) { const fetcher = useFetcher();
// Оптимистично определяем состояние const isCompleted = fetcher.formData ? fetcher.formData.get("completed") === "true" : todo.completed;
return ( <div style={{ opacity: fetcher.state !== "idle" ? 0.7 : 1 }}> <fetcher.Form method="post" action="/todos/toggle"> <input type="hidden" name="id" value={todo.id} /> <input type="hidden" name="completed" value={String(!isCompleted)} /> <button type="submit"> {isCompleted ? "✅" : "⬜"} {todo.title} </button> </fetcher.Form> </div> );}Оптимистичное удаление
Заголовок раздела «Оптимистичное удаление»function TodoList({ todos }: { todos: Todo[] }) { const fetcher = useFetcher();
// Фильтруем удалённый элемент оптимистично const deletingId = fetcher.formData?.get("id"); const optimisticTodos = todos.filter(t => t.id !== deletingId);
return ( <ul> {optimisticTodos.map(todo => ( <li key={todo.id}> {todo.title} <fetcher.Form method="post" action="/todos/delete"> <input type="hidden" name="id" value={todo.id} /> <button type="submit">Удалить</button> </fetcher.Form> </li> ))} </ul> );}Оптимистичное добавление
Заголовок раздела «Оптимистичное добавление»function AddTodo() { const fetcher = useFetcher<{ todo: Todo }>(); const formRef = useRef<HTMLFormElement>(null);
// Сбрасываем форму после успешного сохранения useEffect(() => { if (fetcher.state === "idle" && fetcher.data?.todo) { formRef.current?.reset(); } }, [fetcher.state, fetcher.data]);
// Создаём временный todo пока запрос выполняется const optimisticTodo = fetcher.formData ? { id: "temp", title: fetcher.formData.get("title") as string, completed: false, } : null;
return ( <> {optimisticTodo && <TodoItem todo={optimisticTodo} />} <fetcher.Form ref={formRef} method="post" action="/todos"> <input name="title" placeholder="Новая задача..." required /> <button type="submit">Добавить</button> </fetcher.Form> </> );}useOptimistic (React 19+)
Заголовок раздела «useOptimistic (React 19+)»import { useOptimistic } from "react";import { useFetcher } from "@remix-run/react";
function LikeCounter({ postId, likes }: { postId: string; likes: number }) { const fetcher = useFetcher(); const [optimisticLikes, addLike] = useOptimistic( likes, (state, increment: number) => state + increment );
return ( <fetcher.Form method="post" action="/like" onSubmit={() => addLike(1)} > <input type="hidden" name="postId" value={postId} /> <button type="submit">❤️ {optimisticLikes}</button> </fetcher.Form> );}