React Router 21 ⚛️ Cheatsheet
1. Installation & Setup
# The package is react-router (react-router-dom was removed in v8)
npm i react-router
// Entry point (Data mode)
import { createBrowserRouter } from "react-router";
import { RouterProvider } from "react-router/dom";
import { routeTree } from "./routeTree.gen";
const router = createBrowserRouter([
{ path: "/", Component: Root, errorElement: <RootError />, children: [...] }
]);
createRoot(document.getElementById("root")!).render(
<RouterProvider router={router} />
);
2. Route Configuration
| Property | Purpose |
|---|---|
path | URL pattern (relative when nested) |
Component | React component (preferred) |
element | JSX element |
index | Default child route |
children | Nested routes |
loader | Data fetch before render |
action | Form mutation handler |
errorElement | Route-level error UI |
3. Path Patterns
| Pattern | URL | Params |
|---|---|---|
about | /about | — |
users/:id | /users/1 | { id: "1" } |
posts/:year/:month? | /posts/2026 | { year: "2026" } |
files/* | /files/a/b | { "*": "a/b" } |
* | Any unmatched | — |
4. Navigation Links
import { Link, NavLink, Navigate, useNavigate } from "react-router";
// Declarative
<Link to="/about">About</Link>
<NavLink to="/" end>Home</NavLink>
<NavLink to="/about" className={({ isActive }) => isActive ? "active" : ""}>About</NavLink>
<Navigate to="/login" replace />
// Imperative
const navigate = useNavigate();
navigate("/dashboard", { replace: true });
navigate(-1);
navigate("/checkout", { state: { cartId } });
5. Reading Params and Query
import { useParams, useSearchParams, useLocation } from "react-router";
// Path params (always string)
const { id } = useParams();
const numericId = Number(id);
// Query params
const [searchParams, setSearchParams] = useSearchParams();
const page = Number(searchParams.get("page") || "1");
const tags = searchParams.getAll("tag");
// Update search
setSearchParams({ sort: "price", page: "1" });
setSearchParams(prev => { prev.set("page", "2"); return prev; });
setSearchParams({ page: "2" }, { replace: true });
6. Nested Routes (Layouts)
function DashboardLayout() {
return <div><Sidebar /><Outlet /></div>;
}
const router = createBrowserRouter([
{
path: "/dashboard",
Component: DashboardLayout,
children: [
{ index: true, Component: Home },
{ path: "settings", Component: Settings },
],
},
]);
7. Data Loaders
export async function loader({ params, request }) {
const url = new URL(request.url);
const sort = url.searchParams.get("sort");
return fetch(`/api/posts/${params.id}?sort=${sort}`).then(r => r.json());
}
function Post() {
const post = useLoaderData();
return <h1>{post.title}</h1>;
}
8. Form Actions
export async function action({ request }) {
const formData = await request.formData();
const title = formData.get("title");
if (!title) return { error: "Required" };
await fetch("/api/posts", { method: "POST", body: formData });
return redirect("/posts");
}
function NewPost() {
const data = useActionData();
return (
<Form method="post">
<input name="title" />
{data?.error && <p>{data.error}</p>}
<button>Save</button>
</Form>
);
}
9. Pending States
import { useNavigation, useFetcher, useSubmit } from "react-router";
// Global state
const navigation = useNavigation();
const isSubmitting = navigation.state === "submitting";
const isLoading = navigation.state === "loading";
const optimisticTitle = navigation.formData?.get("title");
// Local fetcher (no navigation)
const fetcher = useFetcher();
<fetcher.Form method="post" action="/like">
<button>{fetcher.state === "submitting" ? "..." : "Like"}</button>
</fetcher.Form>
// Imperative
const submit = useSubmit();
submit(form);
10. Error Boundaries
import { useRouteError, isRouteErrorResponse } from "react-router";
function PostError() {
const error = useRouteError();
if (isRouteErrorResponse(error)) {
return <h1>{error.status} {error.statusText}</h1>;
}
return <p>{error instanceof Error ? error.message : "Unknown"}</p>;
}
// Add to the route
{ path: "/posts/:id", errorElement: <PostError /> }
11. Route Guards
async function authLoader({ context, request }) {
if (!context.auth.isAuthenticated) {
const url = new URL(request.url);
throw redirect(`/login?redirect=${encodeURIComponent(url.pathname)}`);
}
return { user: context.auth.user };
}
// After the login
const redirectTo = searchParams.get("redirect") || "/app";
navigate(redirectTo, { replace: true });
12. Deferred Data (Streaming)
import { defer, Await, useAsyncValue } from "react-router";
import { Suspense } from "react";
async function loader({ params }) {
return defer({
post: await fetchPost(params.id),
comments: fetchComments(params.id),
});
}
// Component
<Suspense fallback={<p>Loading...</p>}>
<Await resolve={comments} errorElement={<p>Failed</p>}>
{(comments) => <Comments data={comments} />}
</Await>
</Suspense>
13. Lazy Loading
// Route-level split
const router = createBrowserRouter([
{ path: "/dashboard", lazy: () => import("./routes/dashboard") },
]);
// Module exports
export function Component() { return <h1>Dashboard</h1>; }
export async function loader() { ... }
export function ErrorBoundary() { ... }
14. Testing (createMemoryRouter)
import { createMemoryRouter, RouterProvider } from "react-router";
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
const router = createMemoryRouter(routes, { initialEntries: ["/posts"] });
render(<RouterProvider router={router} />);
await router.load();
expect(await screen.findByText("First")).toBeInTheDocument();
15. History Fallback (Production)
# Nginx
location / { try_files $uri $uri/ /index.html; }
location /api/ { proxy_pass http://127.0.0.1:3000; }
# Apache
FallbackResource /index.html
# Netlify (public/_redirects)
/* /index.html 200
// Vercel (vercel.json)
{ "rewrites": [{ "source": "/(.*)", "destination": "/index.html" }] }
16. Import Paths
| API | Import From |
|---|---|
BrowserRouter, Routes, Route | react-router |
createBrowserRouter, createMemoryRouter | react-router |
RouterProvider | react-router/dom |
Link, NavLink, Navigate | react-router |
useParams, useLoaderData, useNavigation | react-router |
Form, useFetcher, useActionData | react-router |
redirect, defer, Await | react-router |
17. Key Concepts
| Concept | Description |
|---|---|
| Data mode | createBrowserRouter + loaders + actions |
| Declarative mode | <BrowserRouter> + <Routes> (no loaders) |
| Loaders | Fetch data before render |
| Actions | Handle mutations after form submit |
| Nested routes | Parent <Outlet /> renders children |
| Dynamic segments | :id path parameters |
| Search params | Query string configuration |
| Lazy routes | Code split with lazy: () => import(...) |
| Error boundaries | errorElement per route |
| History fallback | Server config for SPA routing |
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!