React Router v7
React Router v7 cheat sheet covering Library Mode, Framework Mode, data loading, actions, nested routes, and code examples.
Other React Sheets
Installation & Setup
Setting up React Router v7 in Library Mode and Framework Mode
Installing React Router v7 and choosing between Library and Framework modes
# Library Mode (Classic SPA)
npm install react-router
# Framework Mode (Full-stack with Vite)
npm create vite@latest my-app -- --template react
npm install react-router
npm install -D @react-router/dev
// vite.config.ts (Framework Mode)
import { reactRouter } from "@react-router/dev/vite"
import { defineConfig } from "vite"
export default defineConfig({
plugins: [reactRouter()]
})
// Library Mode: main.tsx
import { createBrowserRouter, RouterProvider } from 'react-router'
const router = createBrowserRouter([
{
path: "/",
element: <Root />,
children: [
{ path: "about", element: <About /> },
{ path: "contact", element: <Contact /> }
]
}
])
createRoot(document.getElementById('root')!).render(
<RouterProvider router={router} />
)Routing Basics
Core routing concepts and components
Defining routes and nested routes
// Library Mode: Object-based routes
const router = createBrowserRouter([
{
path: "/",
element: <Root />,
loader: rootLoader,
action: rootAction,
errorElement: <ErrorBoundary />,
children: [
{
index: true, // Default child route
element: <Index />,
},
{
path: "teams",
element: <Teams />,
loader: teamsLoader,
children: [
{
path: ":teamId",
element: <Team />,
loader: teamLoader,
}
]
},
{
path: "*",
element: <NotFound />
}
]
}
])
// Framework Mode: File-based routing
// app/routes/_index.tsx → /
// app/routes/about.tsx → /about
// app/routes/blog.$id.tsx → /blog/:id
// app/routes/$.tsx → catch-allLink, NavLink, and programmatic navigation
import { Link, NavLink, useNavigate, Navigate } from 'react-router'
// Basic navigation
<Link to="/about">About</Link>
<Link to="/users/123">User Profile</Link>
<Link to=".." relative="path">Go Up</Link>
// NavLink with active styles
<NavLink
to="/tasks"
className={({ isActive, isPending }) =>
isActive ? "active" : isPending ? "pending" : ""
}
>
Tasks
</NavLink>
// Programmatic navigation
function LoginForm() {
const navigate = useNavigate()
const handleSubmit = async (e) => {
e.preventDefault()
await login()
navigate("/dashboard", { replace: true })
}
return <form onSubmit={handleSubmit}>...</form>
}Data Loading
Loaders, data fetching, and streaming
Loading data before rendering components
// Basic loader
export async function loader({ params }) {
const user = await fetchUser(params.userId)
if (!user) {
throw new Response("Not Found", { status: 404 })
}
return { user }
}
// Using loaded data
import { useLoaderData } from 'react-router'
export default function User() {
const { user } = useLoaderData<typeof loader>()
return <h1>{user.name}</h1>
}
// Defer streaming data
import { defer, Await } from 'react-router'
import { Suspense } from 'react'
export async function loader() {
return defer({
critical: await fetchCriticalData(),
slow: fetchSlowData() // Don't await
})
}
export default function Page() {
const { critical, slow } = useLoaderData<typeof loader>()
return (
<div>
<h1>{critical.title}</h1>
<Suspense fallback={<Loading />}>
<Await resolve={slow}>
{(data) => <SlowComponent data={data} />}
</Await>
</Suspense>
</div>
)
}Data revalidation and non-navigation data fetching
import { useFetcher, useFetchers, useRevalidator } from 'react-router'
// Fetcher for non-navigation data loading
function NewsletterSignup() {
const fetcher = useFetcher()
return (
<fetcher.Form method="post" action="/newsletter">
<input name="email" type="email" />
<button type="submit">
{fetcher.state === "submitting" ? "Subscribing..." : "Subscribe"}
</button>
</fetcher.Form>
)
}
// Manual revalidation
function RefreshButton() {
const revalidator = useRevalidator()
return (
<button
onClick={() => revalidator.revalidate()}
disabled={revalidator.state === "loading"}
>
{revalidator.state === "loading" ? "Refreshing..." : "Refresh"}
</button>
)
}Forms & Actions
Form handling, actions, and mutations
Handling forms with actions and progressive enhancement
import { Form, useActionData, useNavigation } from 'react-router'
// Action function
export async function action({ request, params }) {
const formData = await request.formData()
const title = formData.get("title")
try {
const post = await createPost({ title })
return redirect(\`/posts/\${post.id}\`)
} catch (error) {
return { error: error.message }
}
}
// Form component
export default function NewPost() {
const actionData = useActionData<typeof action>()
const navigation = useNavigation()
const isSubmitting = navigation.state === "submitting"
return (
<Form method="post">
<input name="title" required />
{actionData?.error && (
<p className="error">{actionData.error}</p>
)}
<button disabled={isSubmitting}>
{isSubmitting ? "Creating..." : "Create Post"}
</button>
</Form>
)
}Error Handling
Error boundaries and error handling patterns
Handling errors in loaders, actions, and components
import { useRouteError, isRouteErrorResponse } from 'react-router'
// Error boundary component
export function ErrorBoundary() {
const error = useRouteError()
if (isRouteErrorResponse(error)) {
return (
<div>
<h1>{error.status} {error.statusText}</h1>
<p>{error.data}</p>
</div>
)
}
return (
<div>
<h1>Oops!</h1>
<p>{error?.message || "Unknown error"}</p>
</div>
)
}
// Throwing errors in loaders
export async function loader({ params }) {
const post = await fetchPost(params.id)
if (!post) {
throw new Response("Not Found", { status: 404 })
}
return { post }
}Framework Mode Features
Server-side rendering, streaming, and framework-specific features
Server-side rendering and HTML streaming in Framework Mode
// app/root.tsx - Framework Mode root
import {
Links,
Meta,
Outlet,
Scripts,
ScrollRestoration,
useLoaderData
} from "react-router"
export function Layout({ children }) {
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<Meta />
<Links />
</head>
<body>
{children}
<ScrollRestoration />
<Scripts />
</body>
</html>
)
}
// Server-only loader
export async function loader() {
// This only runs on the server
const data = await db.query("SELECT * FROM products")
return { products: data }
}
export default function App() {
const { products } = useLoaderData<typeof loader>()
return <Outlet />
}