0
# Data Loading Hooks
1
2
Hooks for accessing route data including loader data, action data, and data fetching utilities with support for revalidation and optimistic updates.
3
4
## Capabilities
5
6
### useLoaderData
7
8
Returns data from the current route's loader function.
9
10
```typescript { .api }
11
/**
12
* Hook that returns data from the current route's loader
13
* @returns Data returned by the route's loader function
14
*/
15
function useLoaderData<T = any>(): T;
16
```
17
18
**Usage Examples:**
19
20
```tsx
21
import { useLoaderData } from "react-router-dom";
22
23
// Route with loader
24
<Route
25
path="/users/:id"
26
element={<UserProfile />}
27
loader={async ({ params }) => {
28
const response = await fetch(`/api/users/${params.id}`);
29
return response.json();
30
}}
31
/>
32
33
// Component using loader data
34
function UserProfile() {
35
const user = useLoaderData<User>();
36
37
return (
38
<div>
39
<h1>{user.name}</h1>
40
<p>{user.email}</p>
41
<p>Joined: {user.joinDate}</p>
42
</div>
43
);
44
}
45
46
interface User {
47
id: string;
48
name: string;
49
email: string;
50
joinDate: string;
51
}
52
```
53
54
### useActionData
55
56
Returns data from the most recent action submission.
57
58
```typescript { .api }
59
/**
60
* Hook that returns data from the most recent action submission
61
* @returns Data returned by the route's action function, or undefined
62
*/
63
function useActionData<T = any>(): T | undefined;
64
```
65
66
**Usage Examples:**
67
68
```tsx
69
import { useActionData, Form } from "react-router-dom";
70
71
// Route with action
72
<Route
73
path="/contact"
74
element={<ContactForm />}
75
action={async ({ request }) => {
76
const formData = await request.formData();
77
try {
78
await submitContact(formData);
79
return { success: true, message: "Message sent!" };
80
} catch (error) {
81
return { success: false, error: error.message };
82
}
83
}}
84
/>
85
86
// Component using action data
87
function ContactForm() {
88
const actionData = useActionData<ActionData>();
89
90
return (
91
<div>
92
<Form method="post">
93
<input name="name" type="text" required />
94
<input name="email" type="email" required />
95
<textarea name="message" required></textarea>
96
<button type="submit">Send</button>
97
</Form>
98
99
{actionData?.success && (
100
<p className="success">{actionData.message}</p>
101
)}
102
103
{actionData?.error && (
104
<p className="error">{actionData.error}</p>
105
)}
106
</div>
107
);
108
}
109
110
interface ActionData {
111
success: boolean;
112
message?: string;
113
error?: string;
114
}
115
```
116
117
### useRouteLoaderData
118
119
Returns loader data from a specific route by ID.
120
121
```typescript { .api }
122
/**
123
* Hook that returns loader data from a specific route by ID
124
* @param routeId - ID of the route to get data from
125
* @returns Loader data from the specified route, or undefined
126
*/
127
function useRouteLoaderData<T = any>(routeId: string): T | undefined;
128
```
129
130
**Usage Examples:**
131
132
```tsx
133
import { useRouteLoaderData } from "react-router-dom";
134
135
// Route configuration with IDs
136
const routes = [
137
{
138
id: "root",
139
path: "/",
140
element: <Root />,
141
loader: rootLoader,
142
children: [
143
{
144
id: "dashboard",
145
path: "dashboard",
146
element: <Dashboard />,
147
loader: dashboardLoader,
148
children: [
149
{
150
path: "profile",
151
element: <Profile />,
152
},
153
],
154
},
155
],
156
},
157
];
158
159
// Access parent route data from child component
160
function Profile() {
161
const rootData = useRouteLoaderData<RootData>("root");
162
const dashboardData = useRouteLoaderData<DashboardData>("dashboard");
163
164
return (
165
<div>
166
<h1>Profile for {rootData?.user.name}</h1>
167
<p>Dashboard stats: {dashboardData?.stats}</p>
168
</div>
169
);
170
}
171
```
172
173
### useFetcher
174
175
Returns a fetcher object for loading data and submitting forms without navigation.
176
177
```typescript { .api }
178
/**
179
* Hook that returns a fetcher for data loading and form submission
180
* @returns Fetcher object with load, submit, and Form components
181
*/
182
function useFetcher<T = any>(): FetcherWithComponents<T>;
183
184
interface FetcherWithComponents<T> {
185
/** Form component bound to this fetcher */
186
Form: React.ComponentType<FetcherFormProps>;
187
/** Function to submit form data */
188
submit: FetcherSubmitFunction;
189
/** Function to load data from a URL */
190
load: (href: string, opts?: { unstable_flushSync?: boolean }) => void;
191
/** Data returned from the fetcher request */
192
data: T;
193
/** Current form data being submitted */
194
formData?: FormData;
195
/** Current JSON data being submitted */
196
json?: unknown;
197
/** Current text data being submitted */
198
text?: string;
199
/** Current form action URL */
200
formAction?: string;
201
/** Current form method */
202
formMethod?: string;
203
/** Current form encoding type */
204
formEncType?: string;
205
/** Current fetcher state */
206
state: "idle" | "loading" | "submitting";
207
}
208
209
interface FetcherFormProps extends Omit<React.FormHTMLAttributes<HTMLFormElement>, 'action' | 'method'> {
210
action?: string;
211
method?: "get" | "post" | "put" | "patch" | "delete";
212
encType?: "application/x-www-form-urlencoded" | "multipart/form-data";
213
replace?: boolean;
214
preventScrollReset?: boolean;
215
}
216
217
type FetcherSubmitFunction = (
218
target: SubmitTarget,
219
options?: FetcherSubmitOptions
220
) => void;
221
222
type SubmitTarget =
223
| HTMLFormElement
224
| FormData
225
| URLSearchParams
226
| { [name: string]: string | File | (string | File)[] };
227
228
interface FetcherSubmitOptions {
229
action?: string;
230
method?: "get" | "post" | "put" | "patch" | "delete";
231
encType?: "application/x-www-form-urlencoded" | "multipart/form-data";
232
replace?: boolean;
233
preventScrollReset?: boolean;
234
unstable_flushSync?: boolean;
235
}
236
```
237
238
**Usage Examples:**
239
240
```tsx
241
import { useFetcher } from "react-router-dom";
242
243
// Load data without navigation
244
function ProductQuickView({ productId }) {
245
const fetcher = useFetcher<Product>();
246
247
const loadProduct = () => {
248
fetcher.load(`/api/products/${productId}`);
249
};
250
251
return (
252
<div>
253
<button onClick={loadProduct}>Load Product</button>
254
255
{fetcher.state === "loading" && <p>Loading...</p>}
256
257
{fetcher.data && (
258
<div>
259
<h3>{fetcher.data.name}</h3>
260
<p>${fetcher.data.price}</p>
261
</div>
262
)}
263
</div>
264
);
265
}
266
267
// Submit form without navigation
268
function AddToCart({ productId }) {
269
const fetcher = useFetcher();
270
271
const isAdding = fetcher.state === "submitting";
272
273
return (
274
<fetcher.Form method="post" action="/api/cart">
275
<input type="hidden" name="productId" value={productId} />
276
<input type="number" name="quantity" defaultValue="1" />
277
<button type="submit" disabled={isAdding}>
278
{isAdding ? "Adding..." : "Add to Cart"}
279
</button>
280
</fetcher.Form>
281
);
282
}
283
284
// Programmatic submission
285
function QuickActions() {
286
const fetcher = useFetcher();
287
288
const likePost = (postId: string) => {
289
fetcher.submit(
290
{ postId, action: "like" },
291
{ method: "post", action: "/api/posts/like" }
292
);
293
};
294
295
return (
296
<button onClick={() => likePost("123")}>
297
Like Post
298
</button>
299
);
300
}
301
```
302
303
### useFetchers
304
305
Returns an array of all active fetchers.
306
307
```typescript { .api }
308
/**
309
* Hook that returns all active fetchers in the application
310
* @returns Array of all active fetcher objects
311
*/
312
function useFetchers(): Fetcher[];
313
314
interface Fetcher<T = any> {
315
data: T;
316
formData?: FormData;
317
json?: unknown;
318
text?: string;
319
formAction?: string;
320
formMethod?: string;
321
formEncType?: string;
322
state: "idle" | "loading" | "submitting";
323
key: string;
324
}
325
```
326
327
**Usage Example:**
328
329
```tsx
330
import { useFetchers } from "react-router-dom";
331
332
function GlobalLoadingIndicator() {
333
const fetchers = useFetchers();
334
335
const activeFetchers = fetchers.filter(
336
fetcher => fetcher.state === "loading" || fetcher.state === "submitting"
337
);
338
339
if (activeFetchers.length === 0) return null;
340
341
return (
342
<div className="loading-indicator">
343
{activeFetchers.length} active requests...
344
</div>
345
);
346
}
347
```
348
349
### useRevalidator
350
351
Returns an object for triggering data revalidation.
352
353
```typescript { .api }
354
/**
355
* Hook that returns revalidation utilities
356
* @returns Revalidator object with state and revalidate function
357
*/
358
function useRevalidator(): Revalidator;
359
360
interface Revalidator {
361
/** Trigger revalidation of all route loaders */
362
revalidate(): void;
363
/** Current revalidation state */
364
state: "idle" | "loading";
365
}
366
```
367
368
**Usage Example:**
369
370
```tsx
371
import { useRevalidator } from "react-router-dom";
372
373
function RefreshButton() {
374
const revalidator = useRevalidator();
375
376
return (
377
<button
378
onClick={() => revalidator.revalidate()}
379
disabled={revalidator.state === "loading"}
380
>
381
{revalidator.state === "loading" ? "Refreshing..." : "Refresh Data"}
382
</button>
383
);
384
}
385
```
386
387
### useNavigation
388
389
Returns the current navigation state.
390
391
```typescript { .api }
392
/**
393
* Hook that returns information about the current navigation
394
* @returns Navigation object with current navigation state
395
*/
396
function useNavigation(): Navigation;
397
398
interface Navigation {
399
/** Current navigation state */
400
state: "idle" | "loading" | "submitting";
401
/** Location being navigated to */
402
location?: Location;
403
/** Form data being submitted */
404
formData?: FormData;
405
/** JSON data being submitted */
406
json?: unknown;
407
/** Text data being submitted */
408
text?: string;
409
/** Form action URL */
410
formAction?: string;
411
/** Form method */
412
formMethod?: string;
413
/** Form encoding type */
414
formEncType?: string;
415
}
416
```
417
418
**Usage Example:**
419
420
```tsx
421
import { useNavigation } from "react-router-dom";
422
423
function GlobalLoadingBar() {
424
const navigation = useNavigation();
425
426
if (navigation.state === "idle") return null;
427
428
return (
429
<div className="loading-bar">
430
{navigation.state === "loading" && "Loading..."}
431
{navigation.state === "submitting" && "Saving..."}
432
</div>
433
);
434
}
435
```
436
437
### useAsyncValue
438
439
Hook for accessing resolved values from Await components in Suspense boundaries.
440
441
```typescript { .api }
442
/**
443
* Hook that returns the resolved value from an Await component
444
* @returns The resolved value from the nearest Await component
445
*/
446
function useAsyncValue<T = any>(): T;
447
```
448
449
**Usage Examples:**
450
451
```tsx
452
import { useAsyncValue, Await } from "react-router-dom";
453
454
// Component that displays resolved async data
455
function UserDetails() {
456
const user = useAsyncValue<User>();
457
458
return (
459
<div>
460
<h2>{user.name}</h2>
461
<p>{user.email}</p>
462
<p>Posts: {user.postCount}</p>
463
</div>
464
);
465
}
466
467
// Usage with Await component
468
function UserProfile() {
469
const data = useLoaderData<{ userPromise: Promise<User> }>();
470
471
return (
472
<div>
473
<h1>User Profile</h1>
474
<Suspense fallback={<div>Loading user...</div>}>
475
<Await resolve={data.userPromise}>
476
<UserDetails />
477
</Await>
478
</Suspense>
479
</div>
480
);
481
}
482
```
483
484
### useAsyncError
485
486
Hook for accessing errors from Await components in error boundaries.
487
488
```typescript { .api }
489
/**
490
* Hook that returns the error from an Await component
491
* @returns The error thrown by the nearest Await component
492
*/
493
function useAsyncError(): unknown;
494
```
495
496
**Usage Examples:**
497
498
```tsx
499
import { useAsyncError, Await } from "react-router-dom";
500
501
// Error boundary component for async errors
502
function AsyncErrorBoundary() {
503
const error = useAsyncError();
504
505
return (
506
<div className="error">
507
<h2>Failed to load data</h2>
508
<p>{error instanceof Error ? error.message : "Unknown error occurred"}</p>
509
<button onClick={() => window.location.reload()}>
510
Try Again
511
</button>
512
</div>
513
);
514
}
515
516
// Usage with Await component and error handling
517
function UserProfileWithError() {
518
const data = useLoaderData<{ userPromise: Promise<User> }>();
519
520
return (
521
<div>
522
<h1>User Profile</h1>
523
<Suspense fallback={<div>Loading...</div>}>
524
<Await
525
resolve={data.userPromise}
526
errorElement={<AsyncErrorBoundary />}
527
>
528
<UserDetails />
529
</Await>
530
</Suspense>
531
</div>
532
);
533
}
534
535
// Loader that returns deferred data
536
export const userLoader = async ({ params }) => {
537
// Return immediately with a promise for deferred loading
538
const userPromise = fetch(`/api/users/${params.id}`)
539
.then(res => {
540
if (!res.ok) throw new Error("Failed to load user");
541
return res.json();
542
});
543
544
return defer({ userPromise });
545
};
546
```
547
548
### Navigation Blocking
549
550
Hooks for blocking navigation conditionally.
551
552
```typescript { .api }
553
/**
554
* Hook that blocks navigation based on a condition
555
* @param when - Condition to block navigation
556
* @param message - Message to show when blocking (legacy browsers)
557
* @returns Blocker object with current blocking state
558
*/
559
function useBlocker(when: boolean | BlockerFunction): Blocker;
560
561
/**
562
* Hook that prompts user before navigation (unstable)
563
* @param when - Condition to show prompt
564
* @param message - Message to show in prompt
565
*/
566
function unstable_usePrompt(when: boolean, message?: string): void;
567
568
type BlockerFunction = (args: BlockerFunctionArgs) => boolean;
569
570
interface BlockerFunctionArgs {
571
currentLocation: Location;
572
nextLocation: Location;
573
historyAction: NavigationType;
574
}
575
576
interface Blocker {
577
state: "unblocked" | "blocked" | "proceeding";
578
proceed(): void;
579
reset(): void;
580
location?: Location;
581
}
582
```
583
584
**Usage Examples:**
585
586
```tsx
587
import { useBlocker, unstable_usePrompt } from "react-router-dom";
588
589
function UnsavedChangesForm() {
590
const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
591
592
// Block navigation when there are unsaved changes
593
const blocker = useBlocker(hasUnsavedChanges);
594
595
// Legacy browser support
596
unstable_usePrompt(
597
hasUnsavedChanges,
598
"You have unsaved changes. Are you sure you want to leave?"
599
);
600
601
return (
602
<div>
603
<form onChange={() => setHasUnsavedChanges(true)}>
604
<input name="title" />
605
<textarea name="content" />
606
<button type="submit">Save</button>
607
</form>
608
609
{blocker.state === "blocked" && (
610
<div className="modal">
611
<p>You have unsaved changes. Are you sure you want to leave?</p>
612
<button onClick={blocker.proceed}>Leave</button>
613
<button onClick={blocker.reset}>Stay</button>
614
</div>
615
)}
616
</div>
617
);
618
}
619
```