Перейти к содержимому

13. Pending UI и Transitions

Remix предоставляет хук useNavigation для отслеживания состояния навигации и отправки форм. Это позволяет показывать индикаторы загрузки и блокировать кнопки во время выполнения запросов.

import { useNavigation } from "@remix-run/react";
export default function Page() {
const navigation = useNavigation();
// Состояния: "idle" | "loading" | "submitting"
const isLoading = navigation.state === "loading";
const isSubmitting = navigation.state === "submitting";
return (
<div>
{isLoading && <div className="progress-bar" />}
<Form method="post">
<button type="submit" disabled={isSubmitting}>
{isSubmitting ? "Сохранение..." : "Сохранить"}
</button>
</Form>
</div>
);
}
// В root.tsx или layout компоненте
import { useNavigation } from "@remix-run/react";
function GlobalLoadingIndicator() {
const navigation = useNavigation();
const isLoading = navigation.state !== "idle";
return (
<div
style={{
position: "fixed",
top: 0, left: 0,
height: "3px",
width: isLoading ? "70%" : "0%",
background: "#3992ff",
transition: isLoading ? "width 0.5s ease" : "width 0.1s ease",
}}
/>
);
}
const navigation = useNavigation();
// Определяем какая именно форма отправляется
const isCreating = navigation.formAction === "/todos" &&
navigation.state === "submitting";
const isDeleting = navigation.formData?.get("intent") === "delete" &&
navigation.state === "submitting";
// Конкретный item
const deletingId = navigation.state === "submitting"
? navigation.formData?.get("todoId")
: null;
function SaveButton({ itemId }) {
const fetcher = useFetcher();
const isSaving = fetcher.state !== "idle";
return (
<fetcher.Form method="post" action="/save">
<input type="hidden" name="id" value={itemId} />
<button type="submit" disabled={isSaving}>
{isSaving ? (
<span>
<Spinner /> Сохранение...
</span>
) : (
"Сохранить"
)}
</button>
</fetcher.Form>
);
}
import { Link, useNavigation } from "@remix-run/react";
function NavItem({ to, children }) {
const navigation = useNavigation();
// Определяем, загружается ли этот маршрут
const isPending = navigation.state === "loading" &&
navigation.location?.pathname === to;
return (
<Link
to={to}
style={{
opacity: isPending ? 0.6 : 1,
fontWeight: isPending ? "bold" : "normal",
}}
>
{isPending ? "⏳ " : ""}{children}
</Link>
);
}