0
# Utilities
1
2
Path manipulation utilities, route matching functions, and data response helpers for advanced routing scenarios.
3
4
## Capabilities
5
6
### Path Utilities
7
8
Functions for working with URL paths and route patterns.
9
10
```typescript { .api }
11
/**
12
* Generate path from pattern and parameters
13
* @param path - Path pattern with parameter placeholders
14
* @param params - Parameter values to substitute
15
* @returns Generated path string
16
*/
17
function generatePath<T extends Record<string, any>>(
18
path: string,
19
params?: T
20
): string;
21
```
22
23
**Usage Examples:**
24
25
```tsx
26
import { generatePath } from "react-router-dom";
27
28
// Basic parameter substitution
29
const userPath = generatePath("/users/:userId", { userId: "123" });
30
// Result: "/users/123"
31
32
// Multiple parameters
33
const postPath = generatePath("/users/:userId/posts/:postId", {
34
userId: "123",
35
postId: "abc"
36
});
37
// Result: "/users/123/posts/abc"
38
39
// Optional parameters
40
const searchPath = generatePath("/search/:category?", { category: "books" });
41
// Result: "/search/books"
42
43
const searchPathEmpty = generatePath("/search/:category?", {});
44
// Result: "/search"
45
46
// Complex patterns
47
const filePath = generatePath("/files/*", { "*": "documents/report.pdf" });
48
// Result: "/files/documents/report.pdf"
49
```
50
51
```typescript { .api }
52
/**
53
* Resolve relative path against current location
54
* @param to - Path to resolve (relative or absolute)
55
* @param fromPathname - Base pathname to resolve from
56
* @returns Resolved path object
57
*/
58
function resolvePath(to: To, fromPathname?: string): Path;
59
60
type To = string | Partial<Path>;
61
62
interface Path {
63
/** Resolved pathname */
64
pathname: string;
65
/** Query string */
66
search: string;
67
/** Hash fragment */
68
hash: string;
69
}
70
```
71
72
**Usage Examples:**
73
74
```tsx
75
import { resolvePath } from "react-router-dom";
76
77
// Resolve relative paths
78
const resolved = resolvePath("../settings", "/users/123/profile");
79
// Result: { pathname: "/users/123/settings", search: "", hash: "" }
80
81
// Resolve with search and hash
82
const resolvedComplex = resolvePath({
83
pathname: "./edit",
84
search: "?tab=general",
85
hash: "#form"
86
}, "/users/123");
87
// Result: { pathname: "/users/123/edit", search: "?tab=general", hash: "#form" }
88
89
// Absolute paths are returned as-is
90
const absolute = resolvePath("/admin/dashboard");
91
// Result: { pathname: "/admin/dashboard", search: "", hash: "" }
92
```
93
94
### Route Matching
95
96
Functions for matching routes against URL paths.
97
98
```typescript { .api }
99
/**
100
* Match path against a pattern
101
* @param pattern - Pattern to match against (string or PathPattern object)
102
* @param pathname - URL pathname to match
103
* @returns Match object with parameters, or null if no match
104
*/
105
function matchPath<T extends Record<string, any>>(
106
pattern: PathPattern<T> | string,
107
pathname: string
108
): PathMatch<T> | null;
109
110
interface PathPattern<T extends Record<string, any> = Record<string, any>> {
111
/** Path pattern with parameter placeholders */
112
path: string;
113
/** Enable case-sensitive matching */
114
caseSensitive?: boolean;
115
/** Match entire path (true) or just beginning (false) */
116
end?: boolean;
117
}
118
119
interface PathMatch<T extends Record<string, any> = Record<string, any>> {
120
/** Extracted route parameters */
121
params: T;
122
/** Matched portion of pathname */
123
pathname: string;
124
/** Base pathname for nested matches */
125
pathnameBase: string;
126
/** Pattern used for matching */
127
pattern: PathPattern<T>;
128
}
129
```
130
131
**Usage Examples:**
132
133
```tsx
134
import { matchPath } from "react-router-dom";
135
136
// Basic pattern matching
137
const match = matchPath("/users/:id", "/users/123");
138
// Result: {
139
// params: { id: "123" },
140
// pathname: "/users/123",
141
// pathnameBase: "/users/123",
142
// pattern: { path: "/users/:id", end: true }
143
// }
144
145
// Pattern with options
146
const optionalMatch = matchPath(
147
{ path: "/posts/:slug", caseSensitive: true, end: false },
148
"/posts/hello-world/comments"
149
);
150
// Result: {
151
// params: { slug: "hello-world" },
152
// pathname: "/posts/hello-world",
153
// pathnameBase: "/posts/hello-world",
154
// pattern: { path: "/posts/:slug", caseSensitive: true, end: false }
155
// }
156
157
// No match
158
const noMatch = matchPath("/users/:id", "/posts/123");
159
// Result: null
160
161
// Wildcard matching
162
const wildcardMatch = matchPath("/files/*", "/files/docs/readme.txt");
163
// Result: {
164
// params: { "*": "docs/readme.txt" },
165
// pathname: "/files/docs/readme.txt",
166
// pathnameBase: "/files",
167
// pattern: { path: "/files/*", end: true }
168
// }
169
```
170
171
```typescript { .api }
172
/**
173
* Match multiple routes against location
174
* @param routes - Array of route objects to match
175
* @param location - Location object or pathname string
176
* @param basename - Base URL for matching
177
* @returns Array of matched routes, or null if no matches
178
*/
179
function matchRoutes(
180
routes: RouteObject[],
181
location: Partial<Location> | string,
182
basename?: string
183
): RouteMatch[] | null;
184
185
interface RouteMatch<ParamKey extends string = string> {
186
/** Matched route parameters */
187
params: Params<ParamKey>;
188
/** Matched pathname portion */
189
pathname: string;
190
/** Base pathname for this match */
191
pathnameBase: string;
192
/** Route definition that matched */
193
route: RouteObject;
194
}
195
196
interface RouteObject {
197
path?: string;
198
index?: boolean;
199
children?: RouteObject[];
200
caseSensitive?: boolean;
201
id?: string;
202
loader?: LoaderFunction;
203
action?: ActionFunction;
204
element?: React.ReactNode | null;
205
errorElement?: React.ReactNode | null;
206
}
207
```
208
209
**Usage Examples:**
210
211
```tsx
212
import { matchRoutes } from "react-router-dom";
213
214
const routes = [
215
{
216
path: "/",
217
children: [
218
{ index: true, element: <Home /> },
219
{ path: "about", element: <About /> },
220
{
221
path: "users/:id",
222
element: <User />,
223
children: [
224
{ path: "profile", element: <Profile /> },
225
{ path: "settings", element: <Settings /> },
226
],
227
},
228
],
229
},
230
];
231
232
// Match nested route
233
const matches = matchRoutes(routes, "/users/123/profile");
234
// Result: [
235
// { params: {}, pathname: "/", pathnameBase: "/", route: routes[0] },
236
// { params: { id: "123" }, pathname: "/users/123", pathnameBase: "/users/123", route: userRoute },
237
// { params: { id: "123" }, pathname: "/users/123/profile", pathnameBase: "/users/123/profile", route: profileRoute }
238
// ]
239
240
// No matches
241
const noMatches = matchRoutes(routes, "/nonexistent");
242
// Result: null
243
```
244
245
### Data Response Helpers
246
247
Functions for creating responses in loaders and actions.
248
249
```typescript { .api }
250
/**
251
* Create response with JSON data
252
* @param data - Data to serialize as JSON
253
* @param init - Response initialization options
254
* @returns Response object with JSON data
255
*/
256
function data<T>(data: T, init?: number | ResponseInit): Response;
257
258
/**
259
* Create redirect response
260
* @param url - URL to redirect to
261
* @param init - Response status code or initialization options
262
* @returns Redirect response
263
*/
264
function redirect(url: string, init?: number | ResponseInit): Response;
265
266
/**
267
* Create document redirect response (full page reload)
268
* @param url - URL to redirect to
269
* @param init - Response initialization options
270
* @returns Document redirect response
271
*/
272
function redirectDocument(url: string, init?: ResponseInit): Response;
273
274
/**
275
* Create replace response (replaces current history entry)
276
* @param url - URL to replace with
277
* @param init - Response initialization options
278
* @returns Replace response
279
*/
280
function replace(url: string, init?: ResponseInit): Response;
281
```
282
283
**Usage Examples:**
284
285
```tsx
286
import { data, redirect, json } from "react-router-dom";
287
288
// Basic data response
289
export const loader = async () => {
290
const users = await fetchUsers();
291
return data(users);
292
};
293
294
// Data response with headers
295
export const loaderWithHeaders = async () => {
296
const posts = await fetchPosts();
297
return data(posts, {
298
headers: {
299
"Cache-Control": "public, max-age=300",
300
"X-Custom-Header": "value",
301
},
302
});
303
};
304
305
// Redirect responses
306
export const actionWithRedirect = async ({ request }) => {
307
const formData = await request.formData();
308
const user = await createUser(formData);
309
310
// Redirect to user profile
311
return redirect(`/users/${user.id}`);
312
};
313
314
// Conditional redirect
315
export const protectedLoader = async ({ request }) => {
316
const user = await getUser(request);
317
318
if (!user) {
319
return redirect("/login");
320
}
321
322
return data({ user });
323
};
324
325
// Error responses
326
export const loaderWithError = async ({ params }) => {
327
const user = await fetchUser(params.id);
328
329
if (!user) {
330
throw data("User not found", { status: 404 });
331
}
332
333
return data(user);
334
};
335
```
336
337
### Error Response Utilities
338
339
Functions for working with error responses.
340
341
```typescript { .api }
342
/**
343
* Check if error is a route error response
344
* @param error - Error object to check
345
* @returns True if error is a route error response
346
*/
347
function isRouteErrorResponse(error: any): error is ErrorResponse;
348
349
interface ErrorResponse {
350
/** HTTP status code */
351
status: number;
352
/** HTTP status text */
353
statusText: string;
354
/** Response data */
355
data: any;
356
/** Internal error flag */
357
internal: boolean;
358
}
359
```
360
361
**Usage Examples:**
362
363
```tsx
364
import { isRouteErrorResponse, useRouteError } from "react-router-dom";
365
366
function ErrorBoundary() {
367
const error = useRouteError();
368
369
if (isRouteErrorResponse(error)) {
370
return (
371
<div>
372
<h1>{error.status} {error.statusText}</h1>
373
<p>{error.data}</p>
374
</div>
375
);
376
}
377
378
// Handle other error types
379
return (
380
<div>
381
<h1>Something went wrong!</h1>
382
<p>{error?.message || "Unknown error"}</p>
383
</div>
384
);
385
}
386
```
387
388
### URL Search Parameters
389
390
Functions for working with URL search parameters.
391
392
```typescript { .api }
393
/**
394
* Create URLSearchParams instance from various input types
395
* @param init - Initial search parameters
396
* @returns URLSearchParams instance
397
*/
398
function createSearchParams(init?: URLSearchParamsInit): URLSearchParams;
399
400
type URLSearchParamsInit =
401
| string
402
| string[][]
403
| Record<string, string | string[]>
404
| URLSearchParams;
405
```
406
407
**Usage Examples:**
408
409
```tsx
410
import { createSearchParams } from "react-router-dom";
411
412
// From string
413
const params1 = createSearchParams("?q=react&category=library");
414
415
// From object
416
const params2 = createSearchParams({
417
q: "react",
418
category: "library",
419
sort: "popularity"
420
});
421
422
// From array of arrays
423
const params3 = createSearchParams([
424
["q", "react"],
425
["category", "library"],
426
["tag", "frontend"],
427
["tag", "javascript"] // Multiple values for same key
428
]);
429
430
// From existing URLSearchParams
431
const existingParams = new URLSearchParams("?page=1");
432
const params4 = createSearchParams(existingParams);
433
434
// Usage in loaders
435
export const loader = async ({ request }) => {
436
const url = new URL(request.url);
437
const searchParams = createSearchParams(url.search);
438
439
const query = searchParams.get("q") || "";
440
const page = parseInt(searchParams.get("page") || "1");
441
442
const results = await searchPosts(query, page);
443
return data({ results, query, page });
444
};
445
```
446
447
### Hook Utilities
448
449
Additional utility functions for working with router hooks.
450
451
```typescript { .api }
452
/**
453
* Create custom link click handler
454
* @param to - Navigation destination
455
* @param options - Navigation options
456
* @returns Click handler function
457
*/
458
function useLinkClickHandler<E extends Element = HTMLAnchorElement>(
459
to: To,
460
options?: {
461
target?: React.HTMLAttributeAnchorTarget;
462
replace?: boolean;
463
state?: any;
464
preventScrollReset?: boolean;
465
relative?: RelativeRoutingType;
466
}
467
): (event: React.MouseEvent<E, MouseEvent>) => void;
468
469
/**
470
* Create form submission handler
471
* @returns Submit function for programmatic form submission
472
*/
473
function useSubmit(): SubmitFunction;
474
475
type SubmitFunction = (
476
target: SubmitTarget,
477
options?: SubmitOptions
478
) => void;
479
480
type SubmitTarget =
481
| HTMLFormElement
482
| FormData
483
| URLSearchParams
484
| { [name: string]: string | File | (string | File)[] };
485
486
interface SubmitOptions {
487
action?: string;
488
method?: "get" | "post" | "put" | "patch" | "delete";
489
encType?: "application/x-www-form-urlencoded" | "multipart/form-data";
490
replace?: boolean;
491
preventScrollReset?: boolean;
492
relative?: RelativeRoutingType;
493
unstable_flushSync?: boolean;
494
}
495
```
496
497
**Usage Examples:**
498
499
```tsx
500
import { useLinkClickHandler, useSubmit } from "react-router-dom";
501
502
// Custom link component
503
function CustomLink({ to, children, ...props }) {
504
const handleClick = useLinkClickHandler(to, {
505
replace: props.replace,
506
state: props.state,
507
});
508
509
return (
510
<a {...props} onClick={handleClick}>
511
{children}
512
</a>
513
);
514
}
515
516
// Programmatic form submission
517
function SearchComponent() {
518
const submit = useSubmit();
519
520
const handleQuickSearch = (query: string) => {
521
submit(
522
{ q: query },
523
{ method: "get", action: "/search" }
524
);
525
};
526
527
const handleFileUpload = (file: File) => {
528
const formData = new FormData();
529
formData.append("file", file);
530
531
submit(formData, {
532
method: "post",
533
action: "/upload",
534
encType: "multipart/form-data",
535
});
536
};
537
538
return (
539
<div>
540
<button onClick={() => handleQuickSearch("react")}>
541
Quick Search
542
</button>
543
<input
544
type="file"
545
onChange={(e) => e.target.files?.[0] && handleFileUpload(e.target.files[0])}
546
/>
547
</div>
548
);
549
}
550
```
551
552
## Utility Patterns
553
554
### Path Generation
555
556
```tsx
557
// Generate paths for navigation
558
const routes = {
559
home: () => "/",
560
user: (id: string) => generatePath("/users/:id", { id }),
561
userPost: (userId: string, postId: string) =>
562
generatePath("/users/:userId/posts/:postId", { userId, postId }),
563
search: (query: string, filters?: Record<string, string>) => {
564
const params = createSearchParams({ q: query, ...filters });
565
return `/search?${params}`;
566
},
567
};
568
569
// Usage
570
<Link to={routes.user("123")}>User Profile</Link>
571
<Link to={routes.userPost("123", "abc")}>User Post</Link>
572
```
573
574
### Route Matching for Components
575
576
```tsx
577
// Conditional rendering based on route matching
578
function NavigationBreadcrumbs() {
579
const location = useLocation();
580
581
const userMatch = matchPath("/users/:id", location.pathname);
582
const postMatch = matchPath("/users/:id/posts/:postId", location.pathname);
583
584
const crumbs = ["Home"];
585
586
if (userMatch) {
587
crumbs.push(`User ${userMatch.params.id}`);
588
}
589
590
if (postMatch) {
591
crumbs.push(`Post ${postMatch.params.postId}`);
592
}
593
594
return (
595
<nav>
596
{crumbs.map((crumb, index) => (
597
<span key={index}>
598
{crumb}
599
{index < crumbs.length - 1 && " > "}
600
</span>
601
))}
602
</nav>
603
);
604
}
605
```
606
607
### Error Response Handling
608
609
```tsx
610
// Comprehensive error handling
611
export const loader = async ({ params }) => {
612
try {
613
const user = await fetchUser(params.id);
614
615
if (!user) {
616
throw data("User not found", {
617
status: 404,
618
statusText: "Not Found"
619
});
620
}
621
622
if (!user.isPublic && !await canViewUser(user.id)) {
623
throw data("Access denied", {
624
status: 403,
625
statusText: "Forbidden"
626
});
627
}
628
629
return data(user);
630
} catch (error) {
631
if (error instanceof Response) {
632
throw error; // Re-throw Response errors
633
}
634
635
console.error("Loader error:", error);
636
throw data("Failed to load user", {
637
status: 500,
638
statusText: "Internal Server Error"
639
});
640
}
641
};
642
```
643
644
## Navigation Constants
645
646
Constants representing different navigation and loading states.
647
648
```typescript { .api }
649
/**
650
* Constant representing idle navigation state
651
*/
652
const IDLE_NAVIGATION: Navigation;
653
654
/**
655
* Constant representing idle fetcher state
656
*/
657
const IDLE_FETCHER: Fetcher;
658
659
/**
660
* Constant representing idle blocker state
661
*/
662
const IDLE_BLOCKER: Blocker;
663
664
interface Navigation {
665
state: "idle" | "loading" | "submitting";
666
location?: Location;
667
formMethod?: FormMethod;
668
formAction?: string;
669
formEncType?: FormEncType;
670
formData?: FormData;
671
}
672
673
interface Fetcher {
674
state: "idle" | "loading" | "submitting";
675
data?: any;
676
formMethod?: FormMethod;
677
formAction?: string;
678
formEncType?: FormEncType;
679
formData?: FormData;
680
}
681
682
interface Blocker {
683
state: "unblocked" | "blocked" | "proceeding";
684
proceed?: () => void;
685
reset?: () => void;
686
location?: Location;
687
}
688
```
689
690
**Usage Examples:**
691
692
```tsx
693
import {
694
IDLE_NAVIGATION,
695
IDLE_FETCHER,
696
IDLE_BLOCKER,
697
useNavigation,
698
useFetcher,
699
useBlocker
700
} from "react-router-dom";
701
702
function NavigationStatus() {
703
const navigation = useNavigation();
704
const fetcher = useFetcher();
705
const blocker = useBlocker(false);
706
707
return (
708
<div>
709
<p>
710
Navigation: {navigation.state === IDLE_NAVIGATION.state ? "Idle" : "Active"}
711
</p>
712
<p>
713
Fetcher: {fetcher.state === IDLE_FETCHER.state ? "Idle" : "Active"}
714
</p>
715
<p>
716
Blocker: {blocker.state === IDLE_BLOCKER.state ? "Unblocked" : blocker.state}
717
</p>
718
</div>
719
);
720
}
721
722
// Checking for idle states in effects
723
function DataComponent() {
724
const navigation = useNavigation();
725
726
useEffect(() => {
727
if (navigation.state === IDLE_NAVIGATION.state) {
728
// Navigation completed, update UI
729
console.log("Navigation completed");
730
}
731
}, [navigation.state]);
732
733
return <div>Content</div>;
734
}
735
```