UNPKG

3.42 kB Markdown View Raw
1---
2title: Pending UI
3order: 7
4---
5
6# Pending UI
7
8[MODES: framework]
9
10## Introduction
11
12When 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
16When 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
19import { useNavigation } from "react-router";
20
21export 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
38Pending 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
41import { NavLink } from "react-router";
42
43function 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
66When 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]
69import { useFetcher } from "react-router";
70
71function 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
87For non-fetcher form submissions, pending states are available on `useNavigation`.
88
89```tsx filename=app/projects/new.tsx
90import { useNavigation, Form } from "react-router";
91
92function 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
110When 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]
113function 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
140Next: [Testing](./testing)
141
142[use_fetcher]: https://api.reactrouter.com/v7/functions/react-router.useFetcher.html
143
\No newline at end of file