0
# Data Loading Hooks
1
2
Hooks for accessing server-loaded data, handling form submissions, and managing client-server communication in Remix applications.
3
4
## Capabilities
5
6
### useLoaderData
7
8
Access data loaded by the current route's loader function.
9
10
```typescript { .api }
11
/**
12
* Returns data loaded by the current route's loader function
13
* Data is automatically serialized and type-safe with proper TypeScript inference
14
* @returns The data returned from the route's loader function
15
*/
16
function useLoaderData<T = unknown>(): SerializeFrom<T>;
17
```
18
19
**Usage Examples:**
20
21
```typescript
22
import { useLoaderData } from "@remix-run/react";
23
import type { LoaderFunctionArgs } from "@remix-run/node";
24
25
// Loader function
26
export async function loader({ params }: LoaderFunctionArgs) {
27
const user = await getUser(params.userId);
28
const posts = await getUserPosts(params.userId);
29
30
return {
31
user,
32
posts,
33
lastLogin: new Date(),
34
};
35
}
36
37
// Component using loader data
38
export default function UserProfile() {
39
const { user, posts, lastLogin } = useLoaderData<typeof loader>();
40
41
return (
42
<div>
43
<h1>{user.name}</h1>
44
<p>Last login: {new Date(lastLogin).toLocaleDateString()}</p>
45
<ul>
46
{posts.map(post => (
47
<li key={post.id}>{post.title}</li>
48
))}
49
</ul>
50
</div>
51
);
52
}
53
```
54
55
### useActionData
56
57
Access data returned by the current route's action function after form submission.
58
59
```typescript { .api }
60
/**
61
* Returns data from the current route's action function
62
* Only available after a form submission, returns undefined otherwise
63
* @returns The data returned from the route's action function or undefined
64
*/
65
function useActionData<T = unknown>(): SerializeFrom<T> | undefined;
66
```
67
68
**Usage Examples:**
69
70
```typescript
71
import { useActionData, Form } from "@remix-run/react";
72
import type { ActionFunctionArgs } from "@remix-run/node";
73
74
// Action function
75
export async function action({ request }: ActionFunctionArgs) {
76
const formData = await request.formData();
77
const email = formData.get("email") as string;
78
79
try {
80
await subscribeToNewsletter(email);
81
return { success: true, message: "Successfully subscribed!" };
82
} catch (error) {
83
return {
84
success: false,
85
message: "Failed to subscribe",
86
errors: { email: "Invalid email address" }
87
};
88
}
89
}
90
91
// Component using action data
92
export default function Newsletter() {
93
const actionData = useActionData<typeof action>();
94
95
return (
96
<div>
97
<Form method="post">
98
<input
99
type="email"
100
name="email"
101
required
102
className={actionData?.errors?.email ? "error" : ""}
103
/>
104
<button type="submit">Subscribe</button>
105
{actionData?.errors?.email && (
106
<span className="error">{actionData.errors.email}</span>
107
)}
108
</Form>
109
110
{actionData?.message && (
111
<div className={actionData.success ? "success" : "error"}>
112
{actionData.message}
113
</div>
114
)}
115
</div>
116
);
117
}
118
```
119
120
### useRouteLoaderData
121
122
Access loader data from any route in the current route hierarchy by route ID.
123
124
```typescript { .api }
125
/**
126
* Returns loader data from a specific route by route ID
127
* Useful for accessing parent route data or shared data across route boundaries
128
* @param routeId - The ID of the route whose loader data to access
129
* @returns The loader data from the specified route or undefined if not found
130
*/
131
function useRouteLoaderData<T = unknown>(routeId: string): SerializeFrom<T> | undefined;
132
```
133
134
**Usage Examples:**
135
136
```typescript
137
import { useRouteLoaderData } from "@remix-run/react";
138
139
// In app/root.tsx
140
export async function loader() {
141
return {
142
user: await getCurrentUser(),
143
environment: process.env.NODE_ENV,
144
};
145
}
146
147
// In any child route component
148
export default function ChildRoute() {
149
// Access root loader data using route ID
150
const rootData = useRouteLoaderData<typeof loader>("root");
151
152
if (!rootData?.user) {
153
return <div>Please log in to continue</div>;
154
}
155
156
return (
157
<div>
158
<p>Welcome, {rootData.user.name}!</p>
159
<p>Environment: {rootData.environment}</p>
160
</div>
161
);
162
}
163
164
// In a nested layout accessing parent data
165
export default function DashboardLayout() {
166
// Access dashboard loader data from child routes
167
const dashboardData = useRouteLoaderData("routes/dashboard");
168
169
return (
170
<div>
171
<nav>Dashboard Navigation</nav>
172
{dashboardData && <UserStats stats={dashboardData.stats} />}
173
<Outlet />
174
</div>
175
);
176
}
177
```
178
179
### useFetcher
180
181
Create a fetcher for loading data or submitting forms without causing navigation.
182
183
```typescript { .api }
184
/**
185
* Creates a fetcher for data loading and form submission without navigation
186
* Provides a way to interact with loaders and actions programmatically
187
* @param opts - Optional fetcher configuration
188
* @returns Fetcher instance with form components and submission methods
189
*/
190
function useFetcher<T = unknown>(opts?: FetcherOptions): FetcherWithComponents<T>;
191
192
interface FetcherOptions {
193
/** Key to identify this fetcher instance for reuse */
194
key?: string;
195
}
196
197
interface FetcherWithComponents<T = unknown> {
198
/** Current state of the fetcher */
199
state: "idle" | "loading" | "submitting";
200
/** Data returned from the last successful fetch */
201
data: SerializeFrom<T> | undefined;
202
/** Form data being submitted */
203
formData: FormData | undefined;
204
/** JSON data being submitted */
205
json: any;
206
/** Text data being submitted */
207
text: string | undefined;
208
/** HTTP method of the current/last submission */
209
formMethod: string | undefined;
210
/** Action URL of the current/last submission */
211
formAction: string | undefined;
212
/** Form component for this fetcher */
213
Form: React.ComponentType<FormProps>;
214
/** Submit function for programmatic submissions */
215
submit: SubmitFunction;
216
/** Load function for programmatic data loading */
217
load: (href: string) => void;
218
}
219
```
220
221
**Usage Examples:**
222
223
```typescript
224
import { useFetcher } from "@remix-run/react";
225
226
// Simple data fetching
227
function UserSearch() {
228
const fetcher = useFetcher();
229
230
const handleSearch = (query: string) => {
231
fetcher.load(`/api/users/search?q=${encodeURIComponent(query)}`);
232
};
233
234
return (
235
<div>
236
<input
237
type="search"
238
onChange={(e) => handleSearch(e.target.value)}
239
placeholder="Search users..."
240
/>
241
242
{fetcher.state === "loading" && <div>Searching...</div>}
243
244
{fetcher.data && (
245
<ul>
246
{fetcher.data.users.map(user => (
247
<li key={user.id}>{user.name}</li>
248
))}
249
</ul>
250
)}
251
</div>
252
);
253
}
254
255
// Form submission without navigation
256
function QuickAddForm() {
257
const fetcher = useFetcher();
258
259
const isAdding = fetcher.state === "submitting";
260
const isSuccess = fetcher.data?.success;
261
262
return (
263
<fetcher.Form method="post" action="/api/quick-add">
264
<input name="title" placeholder="Quick add..." required />
265
<button type="submit" disabled={isAdding}>
266
{isAdding ? "Adding..." : "Add"}
267
</button>
268
269
{isSuccess && <div>Added successfully!</div>}
270
</fetcher.Form>
271
);
272
}
273
274
// Programmatic form submission
275
function LikeButton({ postId }: { postId: string }) {
276
const fetcher = useFetcher();
277
278
const handleLike = () => {
279
fetcher.submit(
280
{ postId, action: "like" },
281
{ method: "post", action: "/api/posts/like" }
282
);
283
};
284
285
return (
286
<button
287
onClick={handleLike}
288
disabled={fetcher.state === "submitting"}
289
>
290
{fetcher.state === "submitting" ? "Liking..." : "Like"}
291
</button>
292
);
293
}
294
```
295
296
### useMatches
297
298
Access information about all matched routes in the current route hierarchy.
299
300
```typescript { .api }
301
/**
302
* Returns information about all matched routes in the current hierarchy
303
* Includes route IDs, pathnames, data, and route-specific metadata
304
* @returns Array of matched route information
305
*/
306
function useMatches(): UIMatch[];
307
308
interface UIMatch {
309
/** Unique identifier for the route */
310
id: string;
311
/** Path pattern that matched */
312
pathname: string;
313
/** Route parameters */
314
params: Params;
315
/** Data from the route's loader */
316
data: unknown;
317
/** Custom route handle data */
318
handle: RouteHandle | undefined;
319
}
320
```
321
322
**Usage Examples:**
323
324
```typescript
325
import { useMatches } from "@remix-run/react";
326
327
// Breadcrumb navigation
328
function Breadcrumbs() {
329
const matches = useMatches();
330
331
const breadcrumbs = matches.filter(match =>
332
match.handle?.breadcrumb
333
).map(match => ({
334
label: match.handle.breadcrumb(match),
335
pathname: match.pathname,
336
}));
337
338
return (
339
<nav>
340
{breadcrumbs.map((crumb, index) => (
341
<span key={crumb.pathname}>
342
{index > 0 && " > "}
343
<Link to={crumb.pathname}>{crumb.label}</Link>
344
</span>
345
))}
346
</nav>
347
);
348
}
349
350
// Page title based on route hierarchy
351
function PageTitle() {
352
const matches = useMatches();
353
354
const titles = matches
355
.map(match => match.handle?.title?.(match))
356
.filter(Boolean);
357
358
const pageTitle = titles.join(" | ");
359
360
useEffect(() => {
361
document.title = pageTitle;
362
}, [pageTitle]);
363
364
return null;
365
}
366
```
367
368
## Type Definitions
369
370
### Serialization Types
371
372
```typescript { .api }
373
/**
374
* Represents the serialized form of data returned from loaders/actions
375
* Handles Date objects, nested objects, and other non-JSON types
376
*/
377
type SerializeFrom<T> = T extends (...args: any[]) => infer R
378
? SerializeFrom<R>
379
: T extends Date
380
? string
381
: T extends object
382
? { [K in keyof T]: SerializeFrom<T[K]> }
383
: T;
384
```
385
386
### Route Handle
387
388
```typescript { .api }
389
interface RouteHandle {
390
/** Function to generate breadcrumb label */
391
breadcrumb?: (match: UIMatch) => React.ReactNode;
392
/** Function to generate page title */
393
title?: (match: UIMatch) => string;
394
/** Custom metadata for the route */
395
[key: string]: any;
396
}
397
```
398
399
### Form Props for Fetcher
400
401
```typescript { .api }
402
interface FormProps extends Omit<React.FormHTMLAttributes<HTMLFormElement>, "onSubmit"> {
403
method?: "get" | "post" | "put" | "patch" | "delete";
404
action?: string;
405
encType?: "application/x-www-form-urlencoded" | "multipart/form-data" | "text/plain";
406
replace?: boolean;
407
preventScrollReset?: boolean;
408
onSubmit?: (event: React.FormEvent<HTMLFormElement>) => void;
409
}
410
```
411
412
## Implementation Notes
413
414
- **Type Safety**: All hooks provide full TypeScript inference when used with typed loader/action functions
415
- **Automatic Revalidation**: Data is automatically revalidated when dependencies change
416
- **Error Handling**: Hooks integrate with Remix's error boundary system for proper error handling
417
- **Concurrent Safety**: Multiple fetchers can operate simultaneously without conflicts
418
- **Memory Management**: Fetcher instances are automatically cleaned up when components unmount
419
- **SSR Compatibility**: All hooks work correctly during server-side rendering and client hydration