Remix Pending UI — Loading States During Navigation
In this tutorial, you will learn about Remix Pending UI. We cover key concepts, practical examples, and best practices to help you master this topic.
Learn Remix pending UI: show loading indicators during page transitions, loader submissions, and action processing for better user experience.
In this lesson, you'll use useNavigation() to detect pending navigation, show loading states, and create smooth transition experiences.
What You'll Learn
How to detect pending navigation, show loading indicators, disable buttons during submission, and implement progress bars.
Why It Matters
Users need feedback that their action is being processed. Pending UI prevents confusion, double-submissions, and makes the app feel responsive.
Real-World Use
DodaZIP shows a global loading bar at the top during navigation and disables form buttons during submission.
flowchart LR
A[User Clicks] --> B{Navigation State}
B -->|Idle| C[Normal UI]
B -->|Submitting| D[Show Loading]
B -->|Loading| E[Show Progress]
D --> F[Action Complete]
E --> G[Page Loaded]
style B fill:#121212,color:#fff
Navigation State
import { useNavigation } from "@remix-run/react";
export default function GlobalLoading() {
const navigation = useNavigation();
const isPending = navigation.state !== "idle";
if (!isPending) return null;
return (
<div style={{
position: "fixed",
top: 0,
left: 0,
right: 0,
height: 3,
background: "blue",
transition: "opacity 0.2s",
}} />
);
}
Form Loading State
import { Form, useNavigation } from "@remix-run/react";
export default function ContactForm() {
const navigation = useNavigation();
const isSubmitting = navigation.state === "submitting";
return (
<Form method="post">
<input name="email" disabled={isSubmitting} />
<textarea name="message" disabled={isSubmitting} />
<button type="submit" disabled={isSubmitting}>
{isSubmitting ? "Sending..." : "Send Message"}
</button>
</Form>
);
}
Loading Spinner Component
export function Spinner() {
const navigation = useNavigation();
const isPending = navigation.state !== "idle";
return (
<div
style={{
opacity: isPending ? 1 : 0,
transition: "opacity 0.2s",
}}
>
<div className="spinner" />
</div>
);
}
Per-Form Loading with useFetcher
import { useFetcher } from "@remix-run/react";
export default function SubscribeForm() {
const fetcher = useFetcher();
const isSubmitting = fetcher.state !== "idle";
return (
<fetcher.Form method="post" action="/newsletter">
<input name="email" disabled={isSubmitting} />
<button type="submit" disabled={isSubmitting}>
{isSubmitting ? "Subscribing..." : "Subscribe"}
</button>
</fetcher.Form>
);
}
Transition Location
Know where the user is navigating:
export default function NavigationLink({ to, children }) {
const navigation = useNavigation();
const isLoading = navigation.location?.pathname === to;
return (
<a
href={to}
style={{ opacity: isLoading ? 0.5 : 1 }}
>
{children}
</a>
);
}
Common Mistakes
- Not disabling form buttons during submission: Without disable, users can double-click and submit the form twice.
- Showing loading for every navigation: Only show loading for actual data-fetching navigations. Link clicks to the same page shouldn't show loading.
- Replacing content with spinner on navigation: The current content should stay visible while the next page loads, not replaced by a spinner.
- Not handling fetcher errors in pending UI: If a fetcher errors, reset the loading state and show the error.
- Over-animating transitions: Brief loading states (under 300ms) don't need animation. Show loading only for perceptible delays.
Practice Questions
What hook tracks the current navigation state? Answer:
useNavigation(). It returns{ state, location, formData, formAction, formMethod }.What values can
navigation.statehave? Answer:"idle"(no navigation),"submitting"(form being submitted),"loading"(loader data loading).How do you disable a form during submission? Answer: Check
navigation.state === "submitting"and setdisabledon inputs and buttons.What is
navigation.locationused for? Answer: It tells you where the user is navigating to, useful for highlighting the active link.
Challenge
Build a navigation system that shows a global progress bar during page transitions, disables all links during navigation, and indicates which link is currently loading.
Mini Project
Create a multi-form settings page where each form has its own submit button. Each form should show its own loading state without affecting the others. Use useFetcher for independent form submissions.
FAQ
What's Next
Learn about Remix Deferred Loading for streaming data and progressive rendering.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro