Declarative routing for React web applications
—
Hooks for accessing route data including loader data, action data, and data fetching utilities with support for revalidation and optimistic updates.
Returns data from the current route's loader function.
/**
* Hook that returns data from the current route's loader
* @returns Data returned by the route's loader function
*/
function useLoaderData<T = any>(): T;Usage Examples:
import { useLoaderData } from "react-router-dom";
// Route with loader
<Route
path="/users/:id"
element={<UserProfile />}
loader={async ({ params }) => {
const response = await fetch(`/api/users/${params.id}`);
return response.json();
}}
/>
// Component using loader data
function UserProfile() {
const user = useLoaderData<User>();
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
<p>Joined: {user.joinDate}</p>
</div>
);
}
interface User {
id: string;
name: string;
email: string;
joinDate: string;
}Returns data from the most recent action submission.
/**
* Hook that returns data from the most recent action submission
* @returns Data returned by the route's action function, or undefined
*/
function useActionData<T = any>(): T | undefined;Usage Examples:
import { useActionData, Form } from "react-router-dom";
// Route with action
<Route
path="/contact"
element={<ContactForm />}
action={async ({ request }) => {
const formData = await request.formData();
try {
await submitContact(formData);
return { success: true, message: "Message sent!" };
} catch (error) {
return { success: false, error: error.message };
}
}}
/>
// Component using action data
function ContactForm() {
const actionData = useActionData<ActionData>();
return (
<div>
<Form method="post">
<input name="name" type="text" required />
<input name="email" type="email" required />
<textarea name="message" required></textarea>
<button type="submit">Send</button>
</Form>
{actionData?.success && (
<p className="success">{actionData.message}</p>
)}
{actionData?.error && (
<p className="error">{actionData.error}</p>
)}
</div>
);
}
interface ActionData {
success: boolean;
message?: string;
error?: string;
}Returns loader data from a specific route by ID.
/**
* Hook that returns loader data from a specific route by ID
* @param routeId - ID of the route to get data from
* @returns Loader data from the specified route, or undefined
*/
function useRouteLoaderData<T = any>(routeId: string): T | undefined;Usage Examples:
import { useRouteLoaderData } from "react-router-dom";
// Route configuration with IDs
const routes = [
{
id: "root",
path: "/",
element: <Root />,
loader: rootLoader,
children: [
{
id: "dashboard",
path: "dashboard",
element: <Dashboard />,
loader: dashboardLoader,
children: [
{
path: "profile",
element: <Profile />,
},
],
},
],
},
];
// Access parent route data from child component
function Profile() {
const rootData = useRouteLoaderData<RootData>("root");
const dashboardData = useRouteLoaderData<DashboardData>("dashboard");
return (
<div>
<h1>Profile for {rootData?.user.name}</h1>
<p>Dashboard stats: {dashboardData?.stats}</p>
</div>
);
}Returns a fetcher object for loading data and submitting forms without navigation.
/**
* Hook that returns a fetcher for data loading and form submission
* @returns Fetcher object with load, submit, and Form components
*/
function useFetcher<T = any>(): FetcherWithComponents<T>;
interface FetcherWithComponents<T> {
/** Form component bound to this fetcher */
Form: React.ComponentType<FetcherFormProps>;
/** Function to submit form data */
submit: FetcherSubmitFunction;
/** Function to load data from a URL */
load: (href: string, opts?: { unstable_flushSync?: boolean }) => void;
/** Data returned from the fetcher request */
data: T;
/** Current form data being submitted */
formData?: FormData;
/** Current JSON data being submitted */
json?: unknown;
/** Current text data being submitted */
text?: string;
/** Current form action URL */
formAction?: string;
/** Current form method */
formMethod?: string;
/** Current form encoding type */
formEncType?: string;
/** Current fetcher state */
state: "idle" | "loading" | "submitting";
}
interface FetcherFormProps extends Omit<React.FormHTMLAttributes<HTMLFormElement>, 'action' | 'method'> {
action?: string;
method?: "get" | "post" | "put" | "patch" | "delete";
encType?: "application/x-www-form-urlencoded" | "multipart/form-data";
replace?: boolean;
preventScrollReset?: boolean;
}
type FetcherSubmitFunction = (
target: SubmitTarget,
options?: FetcherSubmitOptions
) => void;
type SubmitTarget =
| HTMLFormElement
| FormData
| URLSearchParams
| { [name: string]: string | File | (string | File)[] };
interface FetcherSubmitOptions {
action?: string;
method?: "get" | "post" | "put" | "patch" | "delete";
encType?: "application/x-www-form-urlencoded" | "multipart/form-data";
replace?: boolean;
preventScrollReset?: boolean;
unstable_flushSync?: boolean;
}Usage Examples:
import { useFetcher } from "react-router-dom";
// Load data without navigation
function ProductQuickView({ productId }) {
const fetcher = useFetcher<Product>();
const loadProduct = () => {
fetcher.load(`/api/products/${productId}`);
};
return (
<div>
<button onClick={loadProduct}>Load Product</button>
{fetcher.state === "loading" && <p>Loading...</p>}
{fetcher.data && (
<div>
<h3>{fetcher.data.name}</h3>
<p>${fetcher.data.price}</p>
</div>
)}
</div>
);
}
// Submit form without navigation
function AddToCart({ productId }) {
const fetcher = useFetcher();
const isAdding = fetcher.state === "submitting";
return (
<fetcher.Form method="post" action="/api/cart">
<input type="hidden" name="productId" value={productId} />
<input type="number" name="quantity" defaultValue="1" />
<button type="submit" disabled={isAdding}>
{isAdding ? "Adding..." : "Add to Cart"}
</button>
</fetcher.Form>
);
}
// Programmatic submission
function QuickActions() {
const fetcher = useFetcher();
const likePost = (postId: string) => {
fetcher.submit(
{ postId, action: "like" },
{ method: "post", action: "/api/posts/like" }
);
};
return (
<button onClick={() => likePost("123")}>
Like Post
</button>
);
}Returns an array of all active fetchers.
/**
* Hook that returns all active fetchers in the application
* @returns Array of all active fetcher objects
*/
function useFetchers(): Fetcher[];
interface Fetcher<T = any> {
data: T;
formData?: FormData;
json?: unknown;
text?: string;
formAction?: string;
formMethod?: string;
formEncType?: string;
state: "idle" | "loading" | "submitting";
key: string;
}Usage Example:
import { useFetchers } from "react-router-dom";
function GlobalLoadingIndicator() {
const fetchers = useFetchers();
const activeFetchers = fetchers.filter(
fetcher => fetcher.state === "loading" || fetcher.state === "submitting"
);
if (activeFetchers.length === 0) return null;
return (
<div className="loading-indicator">
{activeFetchers.length} active requests...
</div>
);
}Returns an object for triggering data revalidation.
/**
* Hook that returns revalidation utilities
* @returns Revalidator object with state and revalidate function
*/
function useRevalidator(): Revalidator;
interface Revalidator {
/** Trigger revalidation of all route loaders */
revalidate(): void;
/** Current revalidation state */
state: "idle" | "loading";
}Usage Example:
import { useRevalidator } from "react-router-dom";
function RefreshButton() {
const revalidator = useRevalidator();
return (
<button
onClick={() => revalidator.revalidate()}
disabled={revalidator.state === "loading"}
>
{revalidator.state === "loading" ? "Refreshing..." : "Refresh Data"}
</button>
);
}Returns the current navigation state.
/**
* Hook that returns information about the current navigation
* @returns Navigation object with current navigation state
*/
function useNavigation(): Navigation;
interface Navigation {
/** Current navigation state */
state: "idle" | "loading" | "submitting";
/** Location being navigated to */
location?: Location;
/** Form data being submitted */
formData?: FormData;
/** JSON data being submitted */
json?: unknown;
/** Text data being submitted */
text?: string;
/** Form action URL */
formAction?: string;
/** Form method */
formMethod?: string;
/** Form encoding type */
formEncType?: string;
}Usage Example:
import { useNavigation } from "react-router-dom";
function GlobalLoadingBar() {
const navigation = useNavigation();
if (navigation.state === "idle") return null;
return (
<div className="loading-bar">
{navigation.state === "loading" && "Loading..."}
{navigation.state === "submitting" && "Saving..."}
</div>
);
}Hook for accessing resolved values from Await components in Suspense boundaries.
/**
* Hook that returns the resolved value from an Await component
* @returns The resolved value from the nearest Await component
*/
function useAsyncValue<T = any>(): T;Usage Examples:
import { useAsyncValue, Await } from "react-router-dom";
// Component that displays resolved async data
function UserDetails() {
const user = useAsyncValue<User>();
return (
<div>
<h2>{user.name}</h2>
<p>{user.email}</p>
<p>Posts: {user.postCount}</p>
</div>
);
}
// Usage with Await component
function UserProfile() {
const data = useLoaderData<{ userPromise: Promise<User> }>();
return (
<div>
<h1>User Profile</h1>
<Suspense fallback={<div>Loading user...</div>}>
<Await resolve={data.userPromise}>
<UserDetails />
</Await>
</Suspense>
</div>
);
}Hook for accessing errors from Await components in error boundaries.
/**
* Hook that returns the error from an Await component
* @returns The error thrown by the nearest Await component
*/
function useAsyncError(): unknown;Usage Examples:
import { useAsyncError, Await } from "react-router-dom";
// Error boundary component for async errors
function AsyncErrorBoundary() {
const error = useAsyncError();
return (
<div className="error">
<h2>Failed to load data</h2>
<p>{error instanceof Error ? error.message : "Unknown error occurred"}</p>
<button onClick={() => window.location.reload()}>
Try Again
</button>
</div>
);
}
// Usage with Await component and error handling
function UserProfileWithError() {
const data = useLoaderData<{ userPromise: Promise<User> }>();
return (
<div>
<h1>User Profile</h1>
<Suspense fallback={<div>Loading...</div>}>
<Await
resolve={data.userPromise}
errorElement={<AsyncErrorBoundary />}
>
<UserDetails />
</Await>
</Suspense>
</div>
);
}
// Loader that returns deferred data
export const userLoader = async ({ params }) => {
// Return immediately with a promise for deferred loading
const userPromise = fetch(`/api/users/${params.id}`)
.then(res => {
if (!res.ok) throw new Error("Failed to load user");
return res.json();
});
return defer({ userPromise });
};Hooks for blocking navigation conditionally.
/**
* Hook that blocks navigation based on a condition
* @param when - Condition to block navigation
* @param message - Message to show when blocking (legacy browsers)
* @returns Blocker object with current blocking state
*/
function useBlocker(when: boolean | BlockerFunction): Blocker;
/**
* Hook that prompts user before navigation (unstable)
* @param when - Condition to show prompt
* @param message - Message to show in prompt
*/
function unstable_usePrompt(when: boolean, message?: string): void;
type BlockerFunction = (args: BlockerFunctionArgs) => boolean;
interface BlockerFunctionArgs {
currentLocation: Location;
nextLocation: Location;
historyAction: NavigationType;
}
interface Blocker {
state: "unblocked" | "blocked" | "proceeding";
proceed(): void;
reset(): void;
location?: Location;
}Usage Examples:
import { useBlocker, unstable_usePrompt } from "react-router-dom";
function UnsavedChangesForm() {
const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
// Block navigation when there are unsaved changes
const blocker = useBlocker(hasUnsavedChanges);
// Legacy browser support
unstable_usePrompt(
hasUnsavedChanges,
"You have unsaved changes. Are you sure you want to leave?"
);
return (
<div>
<form onChange={() => setHasUnsavedChanges(true)}>
<input name="title" />
<textarea name="content" />
<button type="submit">Save</button>
</form>
{blocker.state === "blocked" && (
<div className="modal">
<p>You have unsaved changes. Are you sure you want to leave?</p>
<button onClick={blocker.proceed}>Leave</button>
<button onClick={blocker.reset}>Stay</button>
</div>
)}
</div>
);
}Install with Tessl CLI
npx tessl i tessl/npm-react-router-dom