| | |

React Router 21 ⚛️ Cheatsheet

⏱️ Estimated read: 4 min | |

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

PropertyPurpose
pathURL pattern (relative when nested)
ComponentReact component (preferred)
elementJSX element
indexDefault child route
childrenNested routes
loaderData fetch before render
actionForm mutation handler
errorElementRoute-level error UI

3. Path Patterns

PatternURLParams
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

APIImport From
BrowserRouter, Routes, Routereact-router
createBrowserRouter, createMemoryRouterreact-router
RouterProviderreact-router/dom
Link, NavLink, Navigatereact-router
useParams, useLoaderData, useNavigationreact-router
Form, useFetcher, useActionDatareact-router
redirect, defer, Awaitreact-router

17. Key Concepts

ConceptDescription
Data modecreateBrowserRouter + loaders + actions
Declarative mode<BrowserRouter> + <Routes> (no loaders)
LoadersFetch data before render
ActionsHandle mutations after form submit
Nested routesParent <Outlet /> renders children
Dynamic segments:id path parameters
Search paramsQuery string configuration
Lazy routesCode split with lazy: () => import(...)
Error boundarieserrorElement per route
History fallbackServer config for SPA routing

Was this helpful?


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!