Declarative routing for React web applications
—
Path manipulation utilities, route matching functions, and data response helpers for advanced routing scenarios.
Functions for working with URL paths and route patterns.
/**
* Generate path from pattern and parameters
* @param path - Path pattern with parameter placeholders
* @param params - Parameter values to substitute
* @returns Generated path string
*/
function generatePath<T extends Record<string, any>>(
path: string,
params?: T
): string;Usage Examples:
import { generatePath } from "react-router-dom";
// Basic parameter substitution
const userPath = generatePath("/users/:userId", { userId: "123" });
// Result: "/users/123"
// Multiple parameters
const postPath = generatePath("/users/:userId/posts/:postId", {
userId: "123",
postId: "abc"
});
// Result: "/users/123/posts/abc"
// Optional parameters
const searchPath = generatePath("/search/:category?", { category: "books" });
// Result: "/search/books"
const searchPathEmpty = generatePath("/search/:category?", {});
// Result: "/search"
// Complex patterns
const filePath = generatePath("/files/*", { "*": "documents/report.pdf" });
// Result: "/files/documents/report.pdf"/**
* Resolve relative path against current location
* @param to - Path to resolve (relative or absolute)
* @param fromPathname - Base pathname to resolve from
* @returns Resolved path object
*/
function resolvePath(to: To, fromPathname?: string): Path;
type To = string | Partial<Path>;
interface Path {
/** Resolved pathname */
pathname: string;
/** Query string */
search: string;
/** Hash fragment */
hash: string;
}Usage Examples:
import { resolvePath } from "react-router-dom";
// Resolve relative paths
const resolved = resolvePath("../settings", "/users/123/profile");
// Result: { pathname: "/users/123/settings", search: "", hash: "" }
// Resolve with search and hash
const resolvedComplex = resolvePath({
pathname: "./edit",
search: "?tab=general",
hash: "#form"
}, "/users/123");
// Result: { pathname: "/users/123/edit", search: "?tab=general", hash: "#form" }
// Absolute paths are returned as-is
const absolute = resolvePath("/admin/dashboard");
// Result: { pathname: "/admin/dashboard", search: "", hash: "" }Functions for matching routes against URL paths.
/**
* Match path against a pattern
* @param pattern - Pattern to match against (string or PathPattern object)
* @param pathname - URL pathname to match
* @returns Match object with parameters, or null if no match
*/
function matchPath<T extends Record<string, any>>(
pattern: PathPattern<T> | string,
pathname: string
): PathMatch<T> | null;
interface PathPattern<T extends Record<string, any> = Record<string, any>> {
/** Path pattern with parameter placeholders */
path: string;
/** Enable case-sensitive matching */
caseSensitive?: boolean;
/** Match entire path (true) or just beginning (false) */
end?: boolean;
}
interface PathMatch<T extends Record<string, any> = Record<string, any>> {
/** Extracted route parameters */
params: T;
/** Matched portion of pathname */
pathname: string;
/** Base pathname for nested matches */
pathnameBase: string;
/** Pattern used for matching */
pattern: PathPattern<T>;
}Usage Examples:
import { matchPath } from "react-router-dom";
// Basic pattern matching
const match = matchPath("/users/:id", "/users/123");
// Result: {
// params: { id: "123" },
// pathname: "/users/123",
// pathnameBase: "/users/123",
// pattern: { path: "/users/:id", end: true }
// }
// Pattern with options
const optionalMatch = matchPath(
{ path: "/posts/:slug", caseSensitive: true, end: false },
"/posts/hello-world/comments"
);
// Result: {
// params: { slug: "hello-world" },
// pathname: "/posts/hello-world",
// pathnameBase: "/posts/hello-world",
// pattern: { path: "/posts/:slug", caseSensitive: true, end: false }
// }
// No match
const noMatch = matchPath("/users/:id", "/posts/123");
// Result: null
// Wildcard matching
const wildcardMatch = matchPath("/files/*", "/files/docs/readme.txt");
// Result: {
// params: { "*": "docs/readme.txt" },
// pathname: "/files/docs/readme.txt",
// pathnameBase: "/files",
// pattern: { path: "/files/*", end: true }
// }/**
* Match multiple routes against location
* @param routes - Array of route objects to match
* @param location - Location object or pathname string
* @param basename - Base URL for matching
* @returns Array of matched routes, or null if no matches
*/
function matchRoutes(
routes: RouteObject[],
location: Partial<Location> | string,
basename?: string
): RouteMatch[] | null;
interface RouteMatch<ParamKey extends string = string> {
/** Matched route parameters */
params: Params<ParamKey>;
/** Matched pathname portion */
pathname: string;
/** Base pathname for this match */
pathnameBase: string;
/** Route definition that matched */
route: RouteObject;
}
interface RouteObject {
path?: string;
index?: boolean;
children?: RouteObject[];
caseSensitive?: boolean;
id?: string;
loader?: LoaderFunction;
action?: ActionFunction;
element?: React.ReactNode | null;
errorElement?: React.ReactNode | null;
}Usage Examples:
import { matchRoutes } from "react-router-dom";
const routes = [
{
path: "/",
children: [
{ index: true, element: <Home /> },
{ path: "about", element: <About /> },
{
path: "users/:id",
element: <User />,
children: [
{ path: "profile", element: <Profile /> },
{ path: "settings", element: <Settings /> },
],
},
],
},
];
// Match nested route
const matches = matchRoutes(routes, "/users/123/profile");
// Result: [
// { params: {}, pathname: "/", pathnameBase: "/", route: routes[0] },
// { params: { id: "123" }, pathname: "/users/123", pathnameBase: "/users/123", route: userRoute },
// { params: { id: "123" }, pathname: "/users/123/profile", pathnameBase: "/users/123/profile", route: profileRoute }
// ]
// No matches
const noMatches = matchRoutes(routes, "/nonexistent");
// Result: nullFunctions for creating responses in loaders and actions.
/**
* Create response with JSON data
* @param data - Data to serialize as JSON
* @param init - Response initialization options
* @returns Response object with JSON data
*/
function data<T>(data: T, init?: number | ResponseInit): Response;
/**
* Create redirect response
* @param url - URL to redirect to
* @param init - Response status code or initialization options
* @returns Redirect response
*/
function redirect(url: string, init?: number | ResponseInit): Response;
/**
* Create document redirect response (full page reload)
* @param url - URL to redirect to
* @param init - Response initialization options
* @returns Document redirect response
*/
function redirectDocument(url: string, init?: ResponseInit): Response;
/**
* Create replace response (replaces current history entry)
* @param url - URL to replace with
* @param init - Response initialization options
* @returns Replace response
*/
function replace(url: string, init?: ResponseInit): Response;Usage Examples:
import { data, redirect, json } from "react-router-dom";
// Basic data response
export const loader = async () => {
const users = await fetchUsers();
return data(users);
};
// Data response with headers
export const loaderWithHeaders = async () => {
const posts = await fetchPosts();
return data(posts, {
headers: {
"Cache-Control": "public, max-age=300",
"X-Custom-Header": "value",
},
});
};
// Redirect responses
export const actionWithRedirect = async ({ request }) => {
const formData = await request.formData();
const user = await createUser(formData);
// Redirect to user profile
return redirect(`/users/${user.id}`);
};
// Conditional redirect
export const protectedLoader = async ({ request }) => {
const user = await getUser(request);
if (!user) {
return redirect("/login");
}
return data({ user });
};
// Error responses
export const loaderWithError = async ({ params }) => {
const user = await fetchUser(params.id);
if (!user) {
throw data("User not found", { status: 404 });
}
return data(user);
};Functions for working with error responses.
/**
* Check if error is a route error response
* @param error - Error object to check
* @returns True if error is a route error response
*/
function isRouteErrorResponse(error: any): error is ErrorResponse;
interface ErrorResponse {
/** HTTP status code */
status: number;
/** HTTP status text */
statusText: string;
/** Response data */
data: any;
/** Internal error flag */
internal: boolean;
}Usage Examples:
import { isRouteErrorResponse, useRouteError } from "react-router-dom";
function ErrorBoundary() {
const error = useRouteError();
if (isRouteErrorResponse(error)) {
return (
<div>
<h1>{error.status} {error.statusText}</h1>
<p>{error.data}</p>
</div>
);
}
// Handle other error types
return (
<div>
<h1>Something went wrong!</h1>
<p>{error?.message || "Unknown error"}</p>
</div>
);
}Functions for working with URL search parameters.
/**
* Create URLSearchParams instance from various input types
* @param init - Initial search parameters
* @returns URLSearchParams instance
*/
function createSearchParams(init?: URLSearchParamsInit): URLSearchParams;
type URLSearchParamsInit =
| string
| string[][]
| Record<string, string | string[]>
| URLSearchParams;Usage Examples:
import { createSearchParams } from "react-router-dom";
// From string
const params1 = createSearchParams("?q=react&category=library");
// From object
const params2 = createSearchParams({
q: "react",
category: "library",
sort: "popularity"
});
// From array of arrays
const params3 = createSearchParams([
["q", "react"],
["category", "library"],
["tag", "frontend"],
["tag", "javascript"] // Multiple values for same key
]);
// From existing URLSearchParams
const existingParams = new URLSearchParams("?page=1");
const params4 = createSearchParams(existingParams);
// Usage in loaders
export const loader = async ({ request }) => {
const url = new URL(request.url);
const searchParams = createSearchParams(url.search);
const query = searchParams.get("q") || "";
const page = parseInt(searchParams.get("page") || "1");
const results = await searchPosts(query, page);
return data({ results, query, page });
};Additional utility functions for working with router hooks.
/**
* Create custom link click handler
* @param to - Navigation destination
* @param options - Navigation options
* @returns Click handler function
*/
function useLinkClickHandler<E extends Element = HTMLAnchorElement>(
to: To,
options?: {
target?: React.HTMLAttributeAnchorTarget;
replace?: boolean;
state?: any;
preventScrollReset?: boolean;
relative?: RelativeRoutingType;
}
): (event: React.MouseEvent<E, MouseEvent>) => void;
/**
* Create form submission handler
* @returns Submit function for programmatic form submission
*/
function useSubmit(): SubmitFunction;
type SubmitFunction = (
target: SubmitTarget,
options?: SubmitOptions
) => void;
type SubmitTarget =
| HTMLFormElement
| FormData
| URLSearchParams
| { [name: string]: string | File | (string | File)[] };
interface SubmitOptions {
action?: string;
method?: "get" | "post" | "put" | "patch" | "delete";
encType?: "application/x-www-form-urlencoded" | "multipart/form-data";
replace?: boolean;
preventScrollReset?: boolean;
relative?: RelativeRoutingType;
unstable_flushSync?: boolean;
}Usage Examples:
import { useLinkClickHandler, useSubmit } from "react-router-dom";
// Custom link component
function CustomLink({ to, children, ...props }) {
const handleClick = useLinkClickHandler(to, {
replace: props.replace,
state: props.state,
});
return (
<a {...props} onClick={handleClick}>
{children}
</a>
);
}
// Programmatic form submission
function SearchComponent() {
const submit = useSubmit();
const handleQuickSearch = (query: string) => {
submit(
{ q: query },
{ method: "get", action: "/search" }
);
};
const handleFileUpload = (file: File) => {
const formData = new FormData();
formData.append("file", file);
submit(formData, {
method: "post",
action: "/upload",
encType: "multipart/form-data",
});
};
return (
<div>
<button onClick={() => handleQuickSearch("react")}>
Quick Search
</button>
<input
type="file"
onChange={(e) => e.target.files?.[0] && handleFileUpload(e.target.files[0])}
/>
</div>
);
}// Generate paths for navigation
const routes = {
home: () => "/",
user: (id: string) => generatePath("/users/:id", { id }),
userPost: (userId: string, postId: string) =>
generatePath("/users/:userId/posts/:postId", { userId, postId }),
search: (query: string, filters?: Record<string, string>) => {
const params = createSearchParams({ q: query, ...filters });
return `/search?${params}`;
},
};
// Usage
<Link to={routes.user("123")}>User Profile</Link>
<Link to={routes.userPost("123", "abc")}>User Post</Link>// Conditional rendering based on route matching
function NavigationBreadcrumbs() {
const location = useLocation();
const userMatch = matchPath("/users/:id", location.pathname);
const postMatch = matchPath("/users/:id/posts/:postId", location.pathname);
const crumbs = ["Home"];
if (userMatch) {
crumbs.push(`User ${userMatch.params.id}`);
}
if (postMatch) {
crumbs.push(`Post ${postMatch.params.postId}`);
}
return (
<nav>
{crumbs.map((crumb, index) => (
<span key={index}>
{crumb}
{index < crumbs.length - 1 && " > "}
</span>
))}
</nav>
);
}// Comprehensive error handling
export const loader = async ({ params }) => {
try {
const user = await fetchUser(params.id);
if (!user) {
throw data("User not found", {
status: 404,
statusText: "Not Found"
});
}
if (!user.isPublic && !await canViewUser(user.id)) {
throw data("Access denied", {
status: 403,
statusText: "Forbidden"
});
}
return data(user);
} catch (error) {
if (error instanceof Response) {
throw error; // Re-throw Response errors
}
console.error("Loader error:", error);
throw data("Failed to load user", {
status: 500,
statusText: "Internal Server Error"
});
}
};Constants representing different navigation and loading states.
/**
* Constant representing idle navigation state
*/
const IDLE_NAVIGATION: Navigation;
/**
* Constant representing idle fetcher state
*/
const IDLE_FETCHER: Fetcher;
/**
* Constant representing idle blocker state
*/
const IDLE_BLOCKER: Blocker;
interface Navigation {
state: "idle" | "loading" | "submitting";
location?: Location;
formMethod?: FormMethod;
formAction?: string;
formEncType?: FormEncType;
formData?: FormData;
}
interface Fetcher {
state: "idle" | "loading" | "submitting";
data?: any;
formMethod?: FormMethod;
formAction?: string;
formEncType?: FormEncType;
formData?: FormData;
}
interface Blocker {
state: "unblocked" | "blocked" | "proceeding";
proceed?: () => void;
reset?: () => void;
location?: Location;
}Usage Examples:
import {
IDLE_NAVIGATION,
IDLE_FETCHER,
IDLE_BLOCKER,
useNavigation,
useFetcher,
useBlocker
} from "react-router-dom";
function NavigationStatus() {
const navigation = useNavigation();
const fetcher = useFetcher();
const blocker = useBlocker(false);
return (
<div>
<p>
Navigation: {navigation.state === IDLE_NAVIGATION.state ? "Idle" : "Active"}
</p>
<p>
Fetcher: {fetcher.state === IDLE_FETCHER.state ? "Idle" : "Active"}
</p>
<p>
Blocker: {blocker.state === IDLE_BLOCKER.state ? "Unblocked" : blocker.state}
</p>
</div>
);
}
// Checking for idle states in effects
function DataComponent() {
const navigation = useNavigation();
useEffect(() => {
if (navigation.state === IDLE_NAVIGATION.state) {
// Navigation completed, update UI
console.log("Navigation completed");
}
}, [navigation.state]);
return <div>Content</div>;
}Install with Tessl CLI
npx tessl i tessl/npm-react-router-dom