Easy Guide to Loaders, Actions, and Routing
React Router v7 is a tool for making navigation in React apps easier. It helps you organize your app better and handle data loading and form submissions clearly. This guide will show you how to set up routes, use loaders and actions, and build a neat app. We will use an example of an app that MLB fans can use to watch game highlights.
In React Router v7, routes help you decide what gets shown on your app. Routes are like maps that tell your app where to go when someone clicks something. Here's how you set them up:
// filepath: ./routes/index.tsx
import { type RouteConfig, index, layout, route } from "@react-router/dev/routes";
export default [
layout("./layouts/main-layout.tsx", [
index("./routes/home.tsx"),
route("teams", "./routes/teams.tsx"),
route("teams/:teamId", "./routes/team-details.tsx"),
route("games/:gameId/highlights", "./routes/game-highlights.tsx"),
]),
] satisfies RouteConfig;
This method makes your routes organized and easy to follow, especially if your app grows bigger.
Loaders and actions help you handle data:
Server-side loaders get information from a server before the page loads:
// routes/team-details.tsx
import type { Route } from "./+types/team-details";
import { fetchTeamData } from "../data";
export async function loader({ params }: Route.LoaderArgs) {
const team = await fetchTeamData(params.teamId);
if (!team) throw new Response("Team not found", { status: 404 });
return team;
}
export default function TeamDetails({ loaderData }: Route.ComponentProps) {
return (
<div>
<h1>{loaderData.name}</h1>
<p>{loaderData.description}</p>
</div>
);
}
Client-side loaders fetch data directly from the user's browser:
// routes/game-highlights.tsx
import type { Route } from "./+types/game-highlights";
export async function clientLoader({ params }: Route.ClientLoaderArgs) {
const res = await fetch(`/api/games/${params.gameId}/highlights`);
if (!res.ok) throw new Response("Highlights not found", { status: 404 });
return res.json();
}
export default function GameHighlights({ loaderData }: Route.ComponentProps) {
return (
<div>
<h1>Game Highlights</h1>
{loaderData.videos.map(video => (
<div key={video.id}>
<h2>{video.title}</h2>
<video src={video.url} controls />
</div>
))}
</div>
);
}
Server-side actions safely handle form submissions:
// routes/create-team.tsx
import type { Route } from "./+types/create-team";
import { redirect, Form } from "react-router";
import { createTeamInDatabase } from "../data";
export async function action({ request }: Route.ActionArgs) {
const formData = await request.formData();
const teamName = formData.get("teamName");
if (!teamName || typeof teamName !== "string" || !teamName.trim()) {
return new Response("Invalid team name", { status: 400 });
}
await createTeamInDatabase(teamName);
return redirect("/teams");
}
export default function CreateTeam() {
return (
<Form method="post">
<label>
Team Name:
<input type="text" name="teamName" required />
</label>
<button type="submit">Create Team</button>
</Form>
);
}
Client-side actions handle simple updates quickly:
// routes/update-profile.tsx
import type { Route } from "./+types/update-profile";
import { updateProfile } from "../api";
import { Form } from "react-router";
export async function clientAction({ request }: Route.ClientActionArgs) {
const formData = await request.formData();
const profileData = {
name: formData.get("name"),
email: formData.get("email"),
};
return await updateProfile(profileData);
}
export default function UpdateProfile({ actionData }: Route.ComponentProps) {
return (
<Form method="post">
<label>Name: <input type="text" name="name" defaultValue={actionData?.name} /></label>
<label>Email: <input type="email" name="email" defaultValue={actionData?.email} /></label>
<button type="submit">Update Profile</button>
</Form>
);
}
React Router automatically gives data to your components:
// Using loaderData
export default function TeamDetails({ loaderData }: Route.ComponentProps) {
return <h1>{loaderData.team.name}</h1>;
}
React Router v7 makes handling data and navigation easy. Using loaders and actions will help you build a clean, easy-to-use app. For more advanced tips, check our guide on server-side authentication with React Router and Firebase.
Have fun coding!