Состояние ожидания и оптимистичный интерфейс
Пока сервер выполняет loader или action, пользователь должен видеть, что что-то происходит, иначе он нажмёт кнопку ещё раз. Хук useNavigation сообщает, идёт ли сейчас переход или отправка формы. Оптимистичный интерфейс идёт дальше: показывает результат сразу, не дожидаясь ответа.
Состояния навигации
useNavigation() возвращает объект, главное поле которого — state:
state | Что происходит |
|---|---|
idle | Ничего, страница готова |
submitting | Форма отправлена, выполняется action |
loading | Загружаются данные новой страницы |
Кнопка во время отправки
// app/routes/new-post.tsx
import { Form, useNavigation } from "react-router";
export default function NewPost() {
const navigation = useNavigation();
const isSubmitting = navigation.state === "submitting";
return (
<Form method="post">
<input name="title" placeholder="Заголовок" />
<textarea name="body" placeholder="Текст статьи" />
<button type="submit" disabled={isSubmitting}>
{isSubmitting ? "Публикую..." : "Опубликовать"}
</button>
</Form>
);
}После нажатия кнопка станет неактивной с текстом «Публикую...», а после ответа сервера откроется страница статьи. На быстром компьютере это мгновение; замедлите сеть во вкладке Network инструментов разработчика, чтобы увидеть надпись.
Индикатор загрузки страниц
Общий индикатор удобно поместить в макет app/routes/layout.tsx:
// app/routes/layout.tsx
import { Outlet, useNavigation } from "react-router";
export default function Layout() {
const navigation = useNavigation();
const isLoading = navigation.state !== "idle";
return (
<>
<header>Мой блог {isLoading && <span>Загрузка...</span>}</header>
<main style={{ opacity: isLoading ? 0.5 : 1 }}>
<Outlet />
</main>
</>
);
}При каждом переходе в шапке появится «Загрузка...», а старая страница станет полупрозрачной, пока не придут новые данные.
Оптимистичный интерфейс
Во время отправки в navigation.formData лежат отправленные поля. Их можно показать сразу:
// app/routes/new-post.tsx
const title = navigation.formData?.get("title");
{title && <p>Публикуется: {String(title)}</p>}Пользователь видит заголовок новой статьи ещё до ответа сервера. Если сервер вернёт ошибку, formData исчезнет, и вместо этой строки отрисуются ошибки проверки.
Частая ошибка
Проверять navigation.state === "submitting" в макете, чтобы показать «Отправка формы» только на нужной странице. Состояние общее для всего приложения: оно будет submitting при отправке любой формы. Чтобы отличить свою форму, сравнивайте ещё и navigation.formAction с адресом, на который она отправляется, — или используйте useFetcher из следующего урока, у которого состояние своё.