React Router v7
React Router v7 cheat sheet covering Library Mode, Framework Mode, data loading, actions, nested routes, and code examples.
Other React Sheets
Sign in to mark items as known and track your progress.
Sign inInstallation & Setup
Setting up React Router v7 in Library Mode and Framework Mode
Installation & Basic Setup
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
Route Configuration
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-allNavigation Components
Link, 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
Loaders & Data Fetching
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>
)
}Revalidation & Fetchers
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
Forms & Actions
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
Error Boundaries
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
SSR & Streaming
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 />
}