| 1 | ---
|
| 2 | title: Pending UI
|
| 3 | order: 7
|
| 4 | ---
|
| 5 |
|
| 6 | # Pending UI
|
| 7 |
|
| 8 | [MODES: framework]
|
| 9 |
|
| 10 | ## Introduction
|
| 11 |
|
| 12 | When the user navigates to a new route, or submits data to an action, the UI should immediately respond to the user's actions with a pending or optimistic state. Application code is responsible for this.
|
| 13 |
|
| 14 | ## Global Pending Navigation
|
| 15 |
|
| 16 | When the user navigates to a new url, the loaders for the next page are awaited before the next page renders. You can get the pending state from `useNavigation`.
|
| 17 |
|
| 18 | ```tsx
|
| 19 | import { useNavigation } from "react-router";
|
| 20 |
|
| 21 | export default function Root() {
|
| 22 | const navigation = useNavigation();
|
| 23 | const isNavigating = Boolean(navigation.location);
|
| 24 |
|
| 25 | return (
|
| 26 | <html>
|
| 27 | <body>
|
| 28 | {isNavigating && <GlobalSpinner />}
|
| 29 | <Outlet />
|
| 30 | </body>
|
| 31 | </html>
|
| 32 | );
|
| 33 | }
|
| 34 | ```
|
| 35 |
|
| 36 | ## Local Pending Navigation
|
| 37 |
|
| 38 | Pending indicators can also be localized to the link. NavLink's children, className, and style props can be functions that receive the pending state.
|
| 39 |
|
| 40 | ```tsx
|
| 41 | import { NavLink } from "react-router";
|
| 42 |
|
| 43 | function Navbar() {
|
| 44 | return (
|
| 45 | <nav>
|
| 46 | <NavLink to="/home">
|
| 47 | {({ isPending }) => (
|
| 48 | <span>Home {isPending && <Spinner />}</span>
|
| 49 | )}
|
| 50 | </NavLink>
|
| 51 | <NavLink
|
| 52 | to="/about"
|
| 53 | style={({ isPending }) => ({
|
| 54 | color: isPending ? "gray" : "black",
|
| 55 | })}
|
| 56 | >
|
| 57 | About
|
| 58 | </NavLink>
|
| 59 | </nav>
|
| 60 | );
|
| 61 | }
|
| 62 | ```
|
| 63 |
|
| 64 | ## Pending Form Submission
|
| 65 |
|
| 66 | When a form is submitted, the UI should immediately respond to the user's actions with a pending state. This is easiest to do with a [fetcher][use_fetcher] form because it has its own independent state (whereas normal forms cause a global navigation).
|
| 67 |
|
| 68 | ```tsx filename=app/project.tsx lines=[10-12]
|
| 69 | import { useFetcher } from "react-router";
|
| 70 |
|
| 71 | function NewProjectForm() {
|
| 72 | const fetcher = useFetcher();
|
| 73 |
|
| 74 | return (
|
| 75 | <fetcher.Form method="post">
|
| 76 | <input type="text" name="title" />
|
| 77 | <button type="submit">
|
| 78 | {fetcher.state !== "idle"
|
| 79 | ? "Submitting..."
|
| 80 | : "Submit"}
|
| 81 | </button>
|
| 82 | </fetcher.Form>
|
| 83 | );
|
| 84 | }
|
| 85 | ```
|
| 86 |
|
| 87 | For non-fetcher form submissions, pending states are available on `useNavigation`.
|
| 88 |
|
| 89 | ```tsx filename=app/projects/new.tsx
|
| 90 | import { useNavigation, Form } from "react-router";
|
| 91 |
|
| 92 | function NewProjectForm() {
|
| 93 | const navigation = useNavigation();
|
| 94 |
|
| 95 | return (
|
| 96 | <Form method="post" action="/projects/new">
|
| 97 | <input type="text" name="title" />
|
| 98 | <button type="submit">
|
| 99 | {navigation.formAction === "/projects/new"
|
| 100 | ? "Submitting..."
|
| 101 | : "Submit"}
|
| 102 | </button>
|
| 103 | </Form>
|
| 104 | );
|
| 105 | }
|
| 106 | ```
|
| 107 |
|
| 108 | ## Optimistic UI
|
| 109 |
|
| 110 | When the future state of the UI is known by the form submission data, an optimistic UI can be implemented for instant UX.
|
| 111 |
|
| 112 | ```tsx filename=app/project.tsx lines=[4-7]
|
| 113 | function Task({ task }) {
|
| 114 | const fetcher = useFetcher();
|
| 115 |
|
| 116 | let isComplete = task.status === "complete";
|
| 117 | if (fetcher.formData) {
|
| 118 | isComplete =
|
| 119 | fetcher.formData.get("status") === "complete";
|
| 120 | }
|
| 121 |
|
| 122 | return (
|
| 123 | <div>
|
| 124 | <div>{task.title}</div>
|
| 125 | <fetcher.Form method="post">
|
| 126 | <button
|
| 127 | name="status"
|
| 128 | value={isComplete ? "incomplete" : "complete"}
|
| 129 | >
|
| 130 | {isComplete ? "Mark Incomplete" : "Mark Complete"}
|
| 131 | </button>
|
| 132 | </fetcher.Form>
|
| 133 | </div>
|
| 134 | );
|
| 135 | }
|
| 136 | ```
|
| 137 |
|
| 138 | ---
|
| 139 |
|
| 140 | Next: [Testing](./testing)
|
| 141 |
|
| 142 | [use_fetcher]: https://api.reactrouter.com/v7/functions/react-router.useFetcher.html
|
| 143 | |
| \ | No newline at end of file |