DEV Community

Pedro Henrique Machado
Pedro Henrique Machado

Posted on

React Router V7: A Crash Course

Mastering React Router v7: A Comprehensive Step-by-Step Tutorial

Welcome to this comprehensive guide on leveraging React Router v7 as a full-fledged framework for building modern, data-driven React applications. Whether you're a beginner aiming to understand the fundamentals or an experienced developer looking to deepen your knowledge, this tutorial covers everything you need to know. We'll walk through each topic step-by-step, complete with insightful explanations and practical code examples.

Prefer watching? Check out this in video form:


Table of Contents

  1. Installation
  2. Routing
  3. Route Module
  4. Rendering Strategies
  5. Data Loading
  6. Actions
  7. Navigating
  8. Pending UI
  9. Testing
  10. Custom Framework

1. Installation

Setting up your React Router v7 project correctly from the start can save you time and headaches later. Most projects begin with a template that provides a solid foundation, including project structure, essential configurations, and helpful scripts.

Steps to Install React Router v7

  1. Create a New Project with the React Router Template:
   npx create-react-router@latest my-react-router-app
Enter fullscreen mode Exit fullscreen mode
  1. Navigate to the Project Directory and Install Dependencies:
   cd my-react-router-app
   npm install
   npm run dev
Enter fullscreen mode Exit fullscreen mode
  1. View the Application:

Open your browser and navigate to http://localhost:5173 to see your running app.

Additional Setup

  • GitHub Repository:

    You can view the template's GitHub repository to understand its structure and manually set up your project if needed.

  • Deployment Templates:

    Explore various deployment-ready templates provided by React Router to deploy your app seamlessly to your preferred hosting service.


2. Routing

Routing is the backbone of any single-page application (SPA). React Router v7 allows you to define routes that map URLs to components, enabling dynamic content rendering based on the user's navigation.

Configuring Routes

Routes are defined in the app/routes.ts file. Each route requires two main parts:

  1. URL Pattern: The path segment that matches the URL.
  2. Route Module: The file that contains the route's logic and UI.

Basic Route Configuration

import { type RouteConfig, route } from "@react-router/dev/routes";

export default [
  route("some/path", "./some/file.tsx"),
  // pattern ^           ^ module file
] satisfies RouteConfig;
Enter fullscreen mode Exit fullscreen mode

Advanced Route Configuration

You can create more complex routing structures using nested routes, layouts, and prefixes.

import {
  type RouteConfig,
  route,
  index,
  layout,
  prefix,
} from "@react-router/dev/routes";

export default [
  index("./home.tsx"),
  route("about", "./about.tsx"),

  layout("./auth/layout.tsx", [
    route("login", "./auth/login.tsx"),
    route("register", "./auth/register.tsx"),
  ]),

  ...prefix("concerts", [
    index("./concerts/home.tsx"),
    route(":city", "./concerts/city.tsx"),
    route("trending", "./concerts/trending.tsx"),
  ]),
] satisfies RouteConfig;
Enter fullscreen mode Exit fullscreen mode
  • index: Defines a default child route.
  • layout: Creates a nested layout without adding to the URL.
  • prefix: Adds a common path prefix to a group of routes.

File System Routing (Optional)

If you prefer defining routes based on your file system's structure, use the @react-router/fs-routes package. This allows for automatic route generation based on your directory and file naming conventions.


3. Route Module

A Route Module is a file that defines the behavior and UI for a specific route. It can contain:

  • Loader Functions: For fetching data before rendering.
  • Action Functions: For handling data mutations like form submissions.
  • Default Exported Components: The UI to render when the route matches.

Example Route Module

// app/routes/team.tsx
import type { Route } from "./+types/team";

export async function loader({ params }: Route.LoaderArgs) {
  let team = await fetchTeam(params.teamId);
  return { name: team.name };
}

export default function Team({ loaderData }: Route.ComponentProps) {
  return <h1>{loaderData.name}</h1>;
}
Enter fullscreen mode Exit fullscreen mode

Nested Routes

Routes can be nested to create complex layouts. The parent route renders an <Outlet /> where child routes will appear.

// app/routes/dashboard.tsx
import { Outlet } from "react-router";

export default function Dashboard() {
  return (
    <div>
      <h1>Dashboard</h1>
      {/* Child routes render here */}
      <Outlet />
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode
// app/routes.ts
export default [
  route("dashboard", "./dashboard.tsx", [
    index("./home.tsx"),
    route("settings", "./settings.tsx"),
  ]),
] satisfies RouteConfig;
Enter fullscreen mode Exit fullscreen mode

Root Route

Every route defined in routes.ts is nested inside the special app/root.tsx module. This root route can provide global layouts, context providers, or error boundaries.


4. Rendering Strategies

React Router v7 supports various rendering strategies to cater to different application needs and deployment environments.

1. Client Side Rendering (CSR)

CSR renders all routes in the browser. This is ideal for Single Page Applications (SPAs) where the server only serves static assets.

// react-router.config.ts
import type { Config } from "@react-router/dev/config";

export default {
  ssr: false,
} satisfies Config;
Enter fullscreen mode Exit fullscreen mode

Benefits:

  • Simpler setup without needing a server for rendering.
  • Faster client-side transitions once the app is loaded.

2. Server Side Rendering (SSR)

SSR renders routes on the server, sending fully populated HTML to the client. This enhances SEO and reduces initial load times.

// react-router.config.ts
import type { Config } from "@react-router/dev/config";

export default {
  ssr: true,
} satisfies Config;
Enter fullscreen mode Exit fullscreen mode

Benefits:

  • Improved SEO as search engines can index fully rendered HTML.
  • Faster initial load times since the client receives fully rendered pages.
  • Better performance on slow devices as the server does the heavy lifting.

Note: SSR requires a server environment that supports rendering React components on the server.

3. Static Pre-rendering

Static Pre-rendering generates static HTML files for specified routes at build time. This is excellent for deploying to static hosting services and improving performance and SEO.

// react-router.config.ts
import type { Config } from "@react-router/dev/config";

export default {
  async prerender() {
    return ["/", "/about", "/contact"];
  },
} satisfies Config;
Enter fullscreen mode Exit fullscreen mode

Benefits:

  • Combines the performance benefits of static sites with the flexibility of React.
  • Ideal for pages that don't change frequently.
  • Simplifies deployment as it can be hosted on any static file server or CDN.

Combining Strategies

You can mix rendering strategies within the same application. For example, statically pre-render some routes while server-rendering others.


5. Data Loading

Efficient data management is crucial for building responsive and dynamic applications. React Router v7 provides powerful data loading capabilities through loaders and clientLoaders.

Loader Functions

Loader Functions fetch data before a route renders. They run on the server during SSR or on the client during navigation.

// app/routes/product.tsx
import type { Route } from "./+types/product";

export async function loader({ params }: Route.LoaderArgs) {
  const res = await fetch(`/api/products/${params.pid}`);
  if (!res.ok) throw new Response("Not Found", { status: 404 });
  return res.json();
}

export default function Product({ loaderData }: Route.ComponentProps) {
  const { name, description } = loaderData;
  return (
    <div>
      <h1>{name}</h1>
      <p>{description}</p>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Client Loader

clientLoader fetches data exclusively on the client side. This is useful for client-only data fetching scenarios.

// app/routes/clientProduct.tsx
import type { Route } from "./+types/product";

export async function clientLoader({ params }: Route.ClientLoaderArgs) {
  const res = await fetch(`/api/products/${params.pid}`);
  return res.json();
}

export default function ClientProduct({ loaderData }: Route.ComponentProps) {
  const { name, description } = loaderData;
  return (
    <div>
      <h1>{name}</h1>
      <p>{description}</p>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Combining Loaders

You can use both loader and clientLoader in the same route. The loader handles SSR or pre-rendering, while clientLoader manages client-side data fetching for subsequent navigations.


6. Actions

Actions handle data mutations like creating, updating, or deleting resources. They work in tandem with forms to process user input and update the application state.

Types of Actions

  1. Server Actions (action): Run on the server and are not included in client bundles.
  2. Client Actions (clientAction): Run exclusively in the browser, ideal for client-side only data mutations.

Client Action Example

// app/routes/updateProject.tsx
import { Form, redirect } from "react-router";
import type { Route } from "./+types/project";

export async function clientAction({ request }: Route.ClientActionArgs) {
  const formData = await request.formData();
  const title = formData.get("title");

  const res = await fetch(`/api/projects/${formData.get("projectId")}`, {
    method: "PUT",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ title }),
  });

  if (!res.ok) {
    return { success: false, message: "Failed to update project." };
  }

  return { success: true };
}

export default function UpdateProject({ loaderData }: Route.ComponentProps) {
  return (
    <Form method="post">
      <input type="hidden" name="projectId" value={loaderData.id} />
      <label>
        Project Title:
        <input type="text" name="title" defaultValue={loaderData.title} />
      </label>
      <button type="submit">Update</button>
    </Form>
  );
}
Enter fullscreen mode Exit fullscreen mode

Server Action Example

// app/routes/deleteProject.tsx
import { redirect } from "react-router";
import type { Route } from "./+types/project";

export async function action({ params }: Route.ActionArgs) {
  const res = await fetch(`/api/projects/${params.projectId}`, {
    method: "DELETE",
  });

  if (!res.ok) {
    throw new Response("Failed to delete project.", { status: 500 });
  }

  return redirect("/projects");
}

export default function DeleteProject() {
  // This component can remain empty as the action handles the redirection
  return null;
}
Enter fullscreen mode Exit fullscreen mode

Calling Actions

  • Using <Form>:
  <Form method="post" action="/projects/123/delete">
    <button type="submit">Delete Project</button>
  </Form>
Enter fullscreen mode Exit fullscreen mode
  • Using useSubmit():
  import { useSubmit } from "react-router";

  function DeleteButton({ projectId }) {
    const submit = useSubmit();

    const handleDelete = () => {
      submit(null, { method: "post", action: `/projects/${projectId}/delete` });
    };

    return <button onClick={handleDelete}>Delete Project</button>;
  }
Enter fullscreen mode Exit fullscreen mode
  • Using <fetcher.Form>:
  import { useFetcher } from "react-router";

  function DeleteProject({ projectId }) {
    const fetcher = useFetcher();

    return (
      <fetcher.Form method="post" action={`/projects/${projectId}/delete`}>
        <button type="submit">Delete Project</button>
      </fetcher.Form>
    );
  }
Enter fullscreen mode Exit fullscreen mode

7. Navigating

Effective navigation is essential for a seamless user experience. React Router v7 offers several components and hooks to manage navigation within your application.

1. <Link>

Use <Link> for simple, unstyled navigation between routes.

import { Link } from "react-router";

export function Home() {
  return (
    <div>
      <h1>Home</h1>
      <Link to="/about">Go to About Page</Link>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

2. <NavLink>

<NavLink> is similar to <Link> but provides styling capabilities based on the active route.

import { NavLink } from "react-router";

export function Navbar() {
  return (
    <nav>
      <NavLink
        to="/"
        end
        className={({ isActive }) => (isActive ? "active" : "")}
      >
        Home
      </NavLink>
      <NavLink
        to="/about"
        className={({ isActive }) => (isActive ? "active" : "")}
      >
        About
      </NavLink>
    </nav>
  );
}
Enter fullscreen mode Exit fullscreen mode

Styling Active Links:

/* styles.css */
.active {
  font-weight: bold;
  color: blue;
}
Enter fullscreen mode Exit fullscreen mode

3. <Form>

Forms can navigate and mutate data based on user input.

import { Form } from "react-router";

export function SearchForm() {
  return (
    <Form method="get" action="/search">
      <input type="text" name="q" placeholder="Search..." />
      <button type="submit">Search</button>
    </Form>
  );
}
Enter fullscreen mode Exit fullscreen mode

4. redirect

Use redirect within loaders or actions to programmatically navigate the user.

import { redirect } from "react-router";

export async function loader({ request }: Route.LoaderArgs) {
  const user = await getUser(request);
  if (!user) {
    return redirect("/login");
  }
  return { userName: user.name };
}
Enter fullscreen mode Exit fullscreen mode

5. useNavigate()

useNavigate is a hook that allows for imperative navigation within your components.

import { useNavigate } from "react-router";

export function LogoutButton() {
  const navigate = useNavigate();

  const handleLogout = () => {
    performLogout();
    navigate("/login");
  };

  return <button onClick={handleLogout}>Logout</button>;
}
Enter fullscreen mode Exit fullscreen mode

8. Pending UI

Providing feedback during data fetching or mutations enhances user experience. React Router v7 offers tools to manage and display pending states.

1. Global Pending Navigation

Use useNavigation to determine if a navigation is in progress and display a global spinner or loader.

import { useNavigation, Outlet } from "react-router";

export default function Root() {
  const navigation = useNavigation();
  const isNavigating = Boolean(navigation.state !== "idle");

  return (
    <div>
      {isNavigating && <div className="spinner">Loading...</div>}
      <Outlet />
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

2. Local Pending Indicators

Display loading indicators next to specific links or buttons when they are in a pending state.

import { NavLink } from "react-router";

export function Navbar() {
  return (
    <nav>
      <NavLink to="/home">
        {({ isPending }) => (
          <>Home {isPending && <span className="spinner"></span>}</>
        )}
      </NavLink>
      <NavLink to="/about">
        {({ isPending }) => (
          <>About {isPending && <span className="spinner"></span>}</>
        )}
      </NavLink>
    </nav>
  );
}
Enter fullscreen mode Exit fullscreen mode

3. Pending Form Submission

Use <fetcher.Form> to manage the pending state of individual form submissions without affecting the entire page.

import { useFetcher } from "react-router";

export function CreatePostForm() {
  const fetcher = useFetcher();
  const isSubmitting = fetcher.state === "submitting";

  return (
    <fetcher.Form method="post" action="/posts/create">
      <input type="text" name="title" placeholder="Post Title" />
      <button type="submit" disabled={isSubmitting}>
        {isSubmitting ? "Creating..." : "Create Post"}
      </button>
    </fetcher.Form>
  );
}
Enter fullscreen mode Exit fullscreen mode

4. Optimistic UI

Implement optimistic updates to reflect changes in the UI immediately, even before the server confirms the action.

import { useFetcher } from "react-router";

export function ToggleFavorite({ post }) {
  const fetcher = useFetcher();
  const isFavorited = fetcher.formData
    ? fetcher.formData.get("favorite") === "true"
    : post.favorite;

  return (
    <fetcher.Form method="post" action={`/posts/${post.id}/toggle-favorite`}>
      <button
        type="submit"
        name="favorite"
        value={isFavorited ? "false" : "true"}
      >
        {isFavorited ? "" : ""}
      </button>
    </fetcher.Form>
  );
}
Enter fullscreen mode Exit fullscreen mode

9. Testing

Ensuring your application behaves as expected is crucial. React Router v7 provides tools to facilitate testing, especially for components that rely on routing context.

Using createRoutesStub

createRoutesStub creates a mock routing environment, allowing you to test components in isolation without needing the entire app.

Example: Testing a Login Form

import { createRoutesStub } from "react-router";
import { render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { LoginForm } from "./LoginForm";

test("LoginForm renders error messages", async () => {
  const USER_MESSAGE = "Username is required";
  const PASSWORD_MESSAGE = "Password is required";

  const Stub = createRoutesStub([
    {
      path: "/login",
      Component: LoginForm,
      action() {
        return {
          errors: {
            username: USER_MESSAGE,
            password: PASSWORD_MESSAGE,
          },
        };
      },
    },
  ]);

  // Render the stub at "/login"
  render(<Stub initialEntries={["/login"]} />);

  // Simulate form submission
  userEvent.click(screen.getByText("Login"));

  // Assert error messages are displayed
  await waitFor(() => screen.findByText(USER_MESSAGE));
  await waitFor(() => screen.findByText(PASSWORD_MESSAGE));
});
Enter fullscreen mode Exit fullscreen mode

Testing Navigation and Data Loading

Ensure your components correctly handle data loading and navigation by mocking route loaders and actions.

import { createRoutesStub } from "react-router";
import { render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { PostList } from "./PostList";

test("PostList deletes a post", async () => {
  const Stub = createRoutesStub([
    {
      path: "/",
      Component: PostList,
      loader() {
        return {
          posts: [
            { id: 1, title: "First Post" },
            { id: 2, title: "Second Post" },
          ],
        };
      },
      action({ params }) {
        if (params.postId === "1") {
          return { isDeleted: true };
        }
        return { isDeleted: false };
      },
    },
  ]);

  render(<Stub initialEntries={["/"]} />);

  const deleteButtons = screen.getAllByText("Delete");

  userEvent.click(deleteButtons[0]);

  await waitFor(() => {
    expect(screen.queryByText("First Post")).not.toBeInTheDocument();
  });

  expect(screen.getByText("Second Post")).toBeInTheDocument();
});
Enter fullscreen mode Exit fullscreen mode

Best Practices for Testing

  1. Mock Loaders and Actions: Ensure that your tests simulate the necessary data fetching and mutations.
  2. Isolate Components: Test components in isolation by providing the required context using stubs.
  3. Simulate User Interactions: Use tools like @testing-library/user-event to mimic real user interactions.
  4. Assert UI Changes: Verify that the UI updates correctly based on different states (e.g., loading, success, error).

10. Custom Framework

React Router v7's extensive features enable it to function as a complete framework for React applications. Here's how it integrates various aspects of development to provide a cohesive and scalable solution.

Comprehensive Features

  1. File-Based or Configuration-Based Routing:

    Choose between defining routes via configuration files or using file-system-based conventions for automatic route generation.

  2. Nested Routes and Layouts:

    Create reusable layouts and component hierarchies by nesting routes, promoting DRY (Don't Repeat Yourself) principles.

  3. Data Loading and Actions:

    Handle data fetching and mutations directly within route modules, streamlining data management and reducing boilerplate.

  4. Rendering Strategies:

    Flexibly choose between client-side rendering, server-side rendering, and static pre-rendering based on your application's needs.

  5. State Management:

    Efficiently manage navigation and form states with built-in hooks like useNavigation, useFetcher, and useSubmit.

  6. Pending UI and Optimistic Updates:

    Enhance user experience by displaying loading states and implementing optimistic UI updates without complex state management.

  7. Testing Utilities:

    Easily test components within a mocked routing context, ensuring reliability and maintainability.

  8. SEO and Performance Optimization:

    Improve SEO with SSR and static pre-rendering, and optimize performance with efficient data fetching and caching strategies.

Building Scalable Applications

By combining these features, React Router v7 allows you to build scalable, maintainable, and high-performance applications with minimal boilerplate. Its design encourages clear data flow, separation of concerns, and component reusability, which are essential for large-scale applications.

Example: Building a Simple Blog

Let's see how these features come together in a simple blog application.

Project Structure

my-react-router-app/
├── app/
│   ├── routes.ts
│   ├── root.tsx
│   ├── data.ts
│   └── layouts/
│       └── navbar.tsx
├── public/
├── package.json
├── tailwind.config.js
└── ...other files
Enter fullscreen mode Exit fullscreen mode

Route Configuration

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

export default [
  layout("layouts/navbar.tsx", {
    clientLoader: "layouts/navbar.tsx:clientLoader",
    clientAction: "layouts/navbar.tsx:clientAction",
    children: [
      index("routes/home.tsx"),
      route("posts/:postId/destroy", "routes/destroy-post.tsx", {
        clientAction: "routes/destroy-post.tsx:clientAction",
      }),
    ],
  }),
] satisfies RouteConfig;
Enter fullscreen mode Exit fullscreen mode

Navbar Layout

// app/layouts/navbar.tsx
import {
  Form,
  Link,
  Outlet,
  useNavigation,
  useSubmit,
  useEffect,
} from "react-router";
import { getPosts, createEmptyPost } from "../data";
import type { Route } from "./+types/navbar";

export async function clientLoader() {
  const posts = await getPosts();
  return { posts };
}

export async function clientAction() {
  const post = await createEmptyPost();
  return { isCreated: true };
}

export default function NavbarLayout({ loaderData }: Route.ComponentProps) {
  const { isCreated } = loaderData;
  const navigation = useNavigation();
  const submit = useSubmit();

  return (
    <>
      <header className="flex items-center justify-between p-4 bg-gray-800 text-white">
        <Link to="/" className="text-xl font-bold hover:text-gray-300">
          Posts Manager
        </Link>
        <nav className="flex items-center gap-4">
          <Form method="post">
            <button
              type="submit"
              className="bg-blue-600 hover:bg-blue-500 transition px-3 py-1 rounded text-white"
            >
              New
            </button>
          </Form>
        </nav>
      </header>
      <main className="p-4">
        <Outlet context={loaderData} />
      </main>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

Home Route

// app/routes/home.tsx
import { Form, useOutletContext } from "react-router";

type LoaderData = {
  posts: Array<{ id: number; title: string }>;
};

export default function Home() {
  const { posts } = useOutletContext<LoaderData>();

  if (!posts.length) {
    return (
      <p className="text-gray-700">
        <i>No posts</i>
      </p>
    );
  }

  return (
    <ul className="space-y-2">
      {posts.map((post) => (
        <li
          key={post.id}
          className="flex items-center justify-between border border-gray-200 p-2 rounded"
        >
          <span className="font-medium">{post.title || <i>No Title</i>}</span>
          <Form method="post" action={`posts/${post.id}/destroy`}>
            <button
              type="submit"
              className="px-3 py-1 bg-red-600 text-white rounded hover:bg-red-500 transition"
              onClick={(e) => {
                const response = confirm(
                  "Are you sure you want to delete this post?"
                );
                if (!response) e.preventDefault();
              }}
            >
              Delete
            </button>
          </Form>
        </li>
      ))}
    </ul>
  );
}
Enter fullscreen mode Exit fullscreen mode

Destroy Post Action

// app/routes/destroy-post.tsx
import { redirect } from "react-router";
import { deletePost } from "../data";
import type { Route } from "./+types/destroy-post";

export async function clientAction({ params }: Route.ClientActionArgs) {
  await deletePost(params.postId);
  return redirect("/");
}

export default function DestroyPost() {
  // This component can remain empty as the action handles the redirection
  return null;
}
Enter fullscreen mode Exit fullscreen mode

Data Management

// app/data.ts
type Post = {
  userId: number;
  id: number;
  title: string;
  body: string;
};

let postsCache: Post[] = [];

export async function getPosts() {
  await new Promise((res) => setTimeout(res, 300)); // Simulate delay
  if (postsCache.length === 0) {
    const res = await fetch("https://jsonplaceholder.typicode.com/posts");
    postsCache = await res.json();
  }
  return postsCache;
}

export async function createEmptyPost() {
  const newPost = {
    userId: 1,
    title: "New Post",
    body: "This is a newly created post",
  };
  const res = await fetch("https://jsonplaceholder.typicode.com/posts", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(newPost),
  });
  const created = await res.json();
  const fullPost = { ...newPost, id: created.id };
  postsCache.unshift(fullPost); // Add new post at the top
  return fullPost;
}

export async function deletePost(id: string | undefined) {
  if (!id) return null;
  await fetch(`https://jsonplaceholder.typicode.com/posts/${id}`, {
    method: "DELETE",
  });
  postsCache = postsCache.filter((p) => p.id !== Number(id));
  return true;
}
Enter fullscreen mode Exit fullscreen mode

Styling with Tailwind CSS

Enhance the visual appeal of your application using Tailwind CSS.

  1. Install Tailwind CSS:
   npm install -D tailwindcss postcss autoprefixer
   npx tailwindcss init -p
Enter fullscreen mode Exit fullscreen mode
  1. Configure tailwind.config.js:
   /** @type {import('tailwindcss').Config} */
   module.exports = {
     content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
     theme: {
       extend: {},
     },
     plugins: [],
   };
Enter fullscreen mode Exit fullscreen mode
  1. Add Tailwind Directives to CSS:
   /* src/index.css */
   @tailwind base;
   @tailwind components;
   @tailwind utilities;
Enter fullscreen mode Exit fullscreen mode
  1. Apply Tailwind Classes:

Use Tailwind's utility classes to style your components, as seen in the Navbar and Home route examples above.


11. Conclusion

By following this step-by-step tutorial, you've learned how to harness the full potential of React Router v7 as a comprehensive framework for your React applications. From setting up your project and configuring routes to managing data, handling actions, and optimizing user experience with pending UI states, React Router provides all the tools you need to build scalable and maintainable applications.

Key Takeaways:

  • Comprehensive Routing: Define and manage routes efficiently using configuration or file-system conventions.
  • Data Management: Utilize loaders and actions to handle data fetching and mutations seamlessly within route modules.
  • Flexible Rendering: Choose between client-side rendering, server-side rendering, and static pre-rendering based on your application's needs.
  • Enhanced User Experience: Implement pending UI states and optimistic updates to provide responsive and intuitive interactions.
  • Testing: Leverage testing utilities to ensure your components and routes behave as expected.
  • Scalability: Build scalable applications by combining React Router's features, promoting clean architecture and maintainability.

For more tutorials, examples, and in-depth guides, be sure to subscribe and check out my YouTube channel Pedrotechnologies.

Top comments (2)

Collapse
 
codewander profile image
Kanishka

For those that hoping to stay within the stable parts of React (CSR/SPA) and don't want to deal with a routing library that churns through changes regularly, there is also the simple alternative library wouter.

Collapse
 
kamalhinduja profile image
Kamal Hinduja

Great Crash Course.