DEV Community

Cover image for Learn React Router v7 by Examples
Tihomir Ivanov
Tihomir Ivanov

Posted on

Learn React Router v7 by Examples

React Router v7 is a powerful data-first, file-based routing framework for React apps. Instead of treating routing as a side concern, it lets you define UI, data fetching, and navigation logic together — just like real-world apps need.

Here’s your developer-friendly cheatsheet, filled with code examples and explanations for where you'd use each one.


1. Define Routes via routes.ts

// routes.ts
import { route, layout, index } from "@react-router/dev/routes";

export default [
  layout("layouts/main.tsx", [
    index("routes/home.tsx"),
    route("posts/:postId", "routes/post.tsx"),
    route("posts/:postId/edit", "routes/edit-post.tsx"),
  ]),
  route("about", "routes/about.tsx"),
];
Enter fullscreen mode Exit fullscreen mode

When to use:

Define your entire app’s routing structure in one place.

layout(...) wraps child routes in shared UI, route(...) maps a path to a component, and index(...) handles default views like dashboards.


2. Layouts + Nested Views via <Outlet />

// layouts/main.tsx
import { Outlet, Link } from "react-router";

export default function MainLayout() {
  return (
    <>
      <nav>
        <Link to="/">Home</Link> | <Link to="/about">About</Link>
      </nav>
      <main>
        <Outlet />
      </main>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

When to use:

To wrap multiple routes under a common layout (like sidebar, header, or auth guard). <Outlet /> is where the child route’s content renders.


3. Data Loading with loader()

// routes/post.tsx
import { getPostById } from "../data";
import type { Route } from "./+types/post";

export async function loader({ params }: Route.LoaderArgs) {
  const post = await getPostById(params.postId);
  if (!post) throw new Response("Not Found", { status: 404 });
  return { post };
}

export default function Post({ loaderData }: Route.ComponentProps) {
  const { post } = loaderData;
  return <h1>{post.title}</h1>;
}
Enter fullscreen mode Exit fullscreen mode

When to use:

For fetching data before rendering the route component. Ideal for detail pages, dashboards, and static content with dynamic params.


4. Mutate Data with action()

// routes/edit-post.tsx
import { updatePost } from "../data";
import { redirect } from "react-router";

export async function action({ params, request }: Route.ActionArgs) {
  const formData = await request.formData();
  const updates = Object.fromEntries(formData);
  await updatePost(params.postId, updates);
  return redirect(`/posts/${params.postId}`);
}
Enter fullscreen mode Exit fullscreen mode

When to use:

For handling form submissions. Use with <form method="post"> to trigger this action on submit and redirect the user afterward.


5. Navigation with <Link>, <NavLink>, and useNavigate

<Link to="/posts/123">View Post</Link>

<NavLink to="/about" className={({ isActive }) => isActive ? "active" : ""}>
  About
</NavLink>

const navigate = useNavigate();
<button onClick={() => navigate(-1)}>Back</button>
Enter fullscreen mode Exit fullscreen mode

When to use:

For any kind of page navigation — links, menus, back buttons, breadcrumbs, or redirects after actions.


6. Forms Without Navigation using useFetcher

import { useFetcher } from "react-router";

function Favorite({ postId, isFavorite }) {
  const fetcher = useFetcher();

  return (
    <fetcher.Form method="post">
      <button name="favorite" value={(!isFavorite).toString()}>
        {isFavorite ? "" : ""}
      </button>
    </fetcher.Form>
  );
}
Enter fullscreen mode Exit fullscreen mode

When to use:

If you want to mutate data (like a "like" button) without changing the URL or navigating away. Works just like a form, but in-place.


7. Optimistic UI with fetcher.formData

const isNowFavorite = fetcher.formData
  ? fetcher.formData.get("favorite") === "true"
  : contact.favorite;
Enter fullscreen mode Exit fullscreen mode

When to use:

To instantly update the UI with the user's action before waiting for the server. If the update fails, React Router will revert it automatically.


8. Show Loading State with useNavigation

import { useNavigation } from "react-router";

const navigation = useNavigation();
const isLoading = navigation.state === "loading";
Enter fullscreen mode Exit fullscreen mode

When to use:

To show a spinner, dim content, or animate transitions while new data or routes are loading. Especially useful for slow network or SSR.


9. Search with GET Form + useSubmit

<Form method="get" role="search" onChange={(e) => submit(e.currentTarget)}>
  <input name="q" placeholder="Search..." defaultValue={q || ""} />
</Form>
Enter fullscreen mode Exit fullscreen mode

When to use:

For search inputs, filters, or pagination where you want the query to update the URL (e.g., /search?q=term). Also supports browser history & refresh.


10. Error Handling via throw new Response(...)

export async function loader({ params }) {
  const data = await getData(params.id);
  if (!data) throw new Response("Not Found", { status: 404 });
  return { data };
}
Enter fullscreen mode Exit fullscreen mode

When to use:

To gracefully handle 404s or validation errors. Throw a Response from loader/action, and React Router will show the error boundary.


Bonus: History Stack Control with replace

submit(formRef, { replace: true });
Enter fullscreen mode Exit fullscreen mode

When to use:

To avoid creating a new browser history entry when submitting a search or form (e.g., instant search updates that shouldn't pollute back button stack).


Enable SSR or Prerendering (Optional)

// react-router.config.ts
export default {
  ssr: true,                  // enable server-side rendering
  prerender: ["/about"],      // prerender this static route
};
Enter fullscreen mode Exit fullscreen mode

When to use:

For SEO, faster time-to-interactive, or static deployments. Works with loader() to fetch and render HTML at build or runtime.


Summary

React Router v7 gives you a unified model for:

  • Navigation with <Link>, <NavLink>, useNavigate
  • Data Fetching with loader()
  • Mutations with action() and <Form>
  • Global and local UI state with useNavigation, useFetcher
  • Optimistic UI with fetcher.formData
  • Rendering strategies: SPA, SSR, Prerender

Pro Tip: You don’t need useState, useEffect, or even useReducer for most CRUD apps. React Router v7 gives you a framework where URL = state, form = API call, and route = data boundary.

Want to Learn More?

If you found this article useful and want to go even deeper into React Router v7, check out my new book:

More Effective React Router: Learn React Router v7 in Depth

More Effective React Router: Learn React Router v7 in Depth

The book covers 125+ practical chapters about:

  • Declarative and Data Routing
  • Loaders and Actions
  • Advanced Navigation Patterns
  • Testing, SSR, Accessibility, and more

Kindle Edition on Amazon
Paperback on Amazon

Built for intermediate to advanced React developers.

Top comments (0)