Feature Overview v6.26.2 (2024)

Client Side Routing

React Router enables "client side routing".

In traditional websites, the browser requests a document from a web server, downloads and evaluates CSS and JavaScript assets, and renders the HTML sent from the server. When the user clicks a link, it starts the process all over again for a new page.

Client side routing allows your app to update the URL from a link click without making another request for another document from the server. Instead, your app can immediately render some new UI and make data requests with fetch to update the page with new information.

This enables faster user experiences because the browser doesn't need to request an entirely new document or re-evaluate CSS and JavaScript assets for the next page. It also enables more dynamic user experiences with things like animation.

Client side routing is enabled by creating a Router and linking/submitting to pages with Link and <Form>:

import * as React from "react";import { createRoot } from "react-dom/client";import { createBrowserRouter, RouterProvider, Route, Link,} from "react-router-dom";const router = createBrowserRouter([ { path: "/", element: ( <div> <h1>Hello World</h1> <Link to="about">About Us</Link> </div> ), }, { path: "about", element: <div>About</div>, },]);createRoot(document.getElementById("root")).render( <RouterProvider router={router} />);

Nested Routes

Nested Routing is the general idea of coupling segments of the URL to component hierarchy and data. React Router's nested routes were inspired by the routing system in Ember.js circa 2014. The Ember team realized that in nearly every case, segments of the URL determine:

  • The layouts to render on the page
  • The data dependencies of those layouts

React Router embraces this convention with APIs for creating nested layouts coupled to URL segments and data.

// Configure nested routes with JSXcreateBrowserRouter( createRoutesFromElements( <Route path="/" element={<Root />}> <Route path="contact" element={<Contact />} /> <Route path="dashboard" element={<Dashboard />} loader={({ request }) => fetch("/api/dashboard.json", { signal: request.signal, }) } /> <Route element={<AuthLayout />}> <Route path="login" element={<Login />} loader={redirectIfUser} /> <Route path="logout" action={logoutUser} /> </Route> </Route> ));// Or use plain objectscreateBrowserRouter([ { path: "/", element: <Root />, children: [ { path: "contact", element: <Contact />, }, { path: "dashboard", element: <Dashboard />, loader: ({ request }) => fetch("/api/dashboard.json", { signal: request.signal, }), }, { element: <AuthLayout />, children: [ { path: "login", element: <Login />, loader: redirectIfUser, }, { path: "logout", action: logoutUser, }, ], }, ], },]);

This visualization might be helpful.

Dynamic Segments

Segments of the URL can be dynamic placeholders that are parsed and provided to various apis.

<Route path="projects/:projectId/tasks/:taskId" />

The two segments with : are dynamic, and provided to the following APIs:

// If the current location is /projects/abc/tasks/3<Route // sent to loaders loader={({ params }) => { params.projectId; // abc params.taskId; // 3 }} // and actions action={({ params }) => { params.projectId; // abc params.taskId; // 3 }} element={<Task />}/>;function Task() { // returned from `useParams` const params = useParams(); params.projectId; // abc params.taskId; // 3}function Random() { const match = useMatch( "/projects/:projectId/tasks/:taskId" ); match.params.projectId; // abc match.params.taskId; // 3}

See:

  • <Route path>
  • <Route loader>
  • <Route action>
  • useParams
  • useMatch

Ranked Route Matching

When matching URLs to routes, React Router will rank the routes according to the number of segments, static segments, dynamic segments, splats, etc. and pick the most specific match.

For example, consider these two routes:

<Route path="/teams/:teamId" /><Route path="/teams/new" />

Now consider the URL is http://example.com/teams/new.

Even though both routes technically match the URL (new could be the :teamId), you intuitively know that we want the second route (/teams/new) to be picked. React Router's matching algorithm knows that, too.

With ranked routes, you don't have to worry about route ordering.

Active Links

Most web apps have persistent navigation sections at the top of the UI, the sidebar, and often multiple levels. Styling the active navigation items so the user knows where they are (isActive) or where they're going (isPending) in the app is done easily with <NavLink>.

<NavLink style={({ isActive, isPending }) => { return { color: isActive ? "red" : "inherit", }; }} className={({ isActive, isPending }) => { return isActive ? "active" : isPending ? "pending" : ""; }}/>

You can also useMatch for any other "active" indication outside of links.

function SomeComp() { const match = useMatch("/messages"); return <li className={Boolean(match) ? "active" : ""} />;}

See:

  • NavLink
  • useMatch

Relative Links

Like HTML <a href>, <Link to> and <NavLink to> can take relative paths, with enhanced behavior with nested routes.

Given the following route config:

<Route path="home" element={<Home />}> <Route path="project/:projectId" element={<Project />}> <Route path=":taskId" element={<Task />} /> </Route></Route>

Consider the url https://example.com/home/project/123, which renders the following route component hierarchy:

<Home> <Project /></Home>

If <Project /> renders the following links, the hrefs of the links will resolve like so:

In <Project> @ /home/project/123Resolved <a href>
<Link to="abc">/home/project/123/abc
<Link to=".">/home/project/123
<Link to="..">/home
<Link to=".." relative="path">/home/project

Note that the first .. removes both segments of the project/:projectId route. By default, the .. in relative links traverse the route hierarchy, not the URL segments. Adding relative="path" in the next example allows you to traverse the path segments instead.

Relative links are always relative to the route path they are rendered in, not to the full URL. That means if the user navigates deeper with <Link to="abc"> to <Task /> at the URL /home/project/123/abc, the hrefs in <Project> will not change (contrary to plain <a href>, a common problem with client side routers).

Data Loading

Because URL segments usually map to your app's persistent data, React Router provides conventional data loading hooks to initiate data loading during a navigation. Combined with nested routes, all of the data for multiple layouts at a specific URL can be loaded in parallel.

<Route path="/" loader={async ({ request }) => { // loaders can be async functions const res = await fetch("/api/user.json", { signal: request.signal, }); const user = await res.json(); return user; }} element={<Root />}> <Route path=":teamId" // loaders understand Fetch Responses and will automatically // unwrap the res.json(), so you can simply return a fetch loader={({ params }) => { return fetch(`/api/teams/${params.teamId}`); }} element={<Team />} > <Route path=":gameId" loader={({ params }) => { // of course you can use any data store return fakeSdk.getTeam(params.gameId); }} element={<Game />} /> </Route></Route>

Data is made available to your components through useLoaderData.

function Root() { const user = useLoaderData(); // data from <Route path="/">}function Team() { const team = useLoaderData(); // data from <Route path=":teamId">}function Game() { const game = useLoaderData(); // data from <Route path=":gameId">}

When the user visits or clicks links to https://example.com/real-salt-lake/45face3, all three route loaders will be called and loaded in parallel, before the UI for that URL renders.

Redirects

While loading or changing data, it's common to redirect the user to a different route.

<Route path="dashboard" loader={async () => { const user = await fake.getUser(); if (!user) { // if you know you can't render the route, you can // throw a redirect to stop executing code here, // sending the user to a new route throw redirect("/login"); } // otherwise continue const stats = await fake.getDashboardStats(); return { user, stats }; }}/>
<Route path="project/new" action={async ({ request }) => { const data = await request.formData(); const newProject = await createProject(data); // it's common to redirect after actions complete, // sending the user to the new record return redirect(`/projects/${newProject.id}`); }}/>

See:

  • redirect
  • Throwing in Loaders
  • useNavigate

Pending Navigation UI

When users navigate around the app, the data for the next page is loaded before the page is rendered. It's important to provide user feedback during this time so the app doesn't feel like it's unresponsive.

function Root() { const navigation = useNavigation(); return ( <div> {navigation.state === "loading" && <GlobalSpinner />} <FakeSidebar /> <Outlet /> <FakeFooter /> </div> );}

See:

  • useNavigation

Skeleton UI with <Suspense>

Instead of waiting for the data for the next page, you can defer data so the UI flips over to the next screen with placeholder UI immediately while the data loads.

<Route path="issue/:issueId" element={<Issue />} loader={async ({ params }) => { // these are promises, but *not* awaited const comments = fake.getIssueComments(params.issueId); const history = fake.getIssueHistory(params.issueId); // the issue, however, *is* awaited const issue = await fake.getIssue(params.issueId); // defer enables suspense for the un-awaited promises return defer({ issue, comments, history }); }}/>;function Issue() { const { issue, history, comments } = useLoaderData(); return ( <div> <IssueDescription issue={issue} /> {/* Suspense provides the placeholder fallback */} <Suspense fallback={<IssueHistorySkeleton />}> {/* Await manages the deferred data (promise) */} <Await resolve={history}> {/* this calls back when the data is resolved */} {(resolvedHistory) => ( <IssueHistory history={resolvedHistory} /> )} </Await> </Suspense> <Suspense fallback={<IssueCommentsSkeleton />}> <Await resolve={comments}> {/* ... or you can use hooks to access the data */} <IssueComments /> </Await> </Suspense> </div> );}function IssueComments() { const comments = useAsyncValue(); return <div>{/* ... */}</div>;}

See

  • Deferred Data Guide
  • defer
  • Await
  • useAsyncValue

Data Mutations

HTML forms are navigation events, just like links. React Router supports HTML form workflows with client side routing.

When a form is submitted, the normal browser navigation event is prevented and a Request, with a body containing the FormData of the submission, is created. This request is sent to the <Route action> that matches the form's <Form action>.

Form elements's name prop are submitted to the action:

<Form action="/project/new"> <label> Project title <br /> <input type="text" name="title" /> </label> <label> Target Finish Date <br /> <input type="date" name="due" /> </label></Form>

The normal HTML document request is prevented and sent to the matching route's action (<Route path> that matches the <form action>), including the request.formData.

<Route path="project/new" action={async ({ request }) => { const formData = await request.formData(); const newProject = await createProject({ title: formData.get("title"), due: formData.get("due"), }); return redirect(`/projects/${newProject.id}`); }}/>

Data Revalidation

Decades old web conventions indicate that when a form is posted to the server, data is changing and a new page is rendered. That convention is followed in React Router's HTML-based data mutation APIs.

After route actions are called, the loaders for all of the data on the page is called again to ensure the UI stays up-to-date with the data automatically. No cache keys to expire, no context providers to reload.

See:

  • Tutorial "Creating Contacts"

Busy Indicators

When forms are being submitted to route actions, you have access to the navigation state to display busy indicators, disable fieldsets, etc.

function NewProjectForm() { const navigation = useNavigation(); const busy = navigation.state === "submitting"; return ( <Form action="/project/new"> <fieldset disabled={busy}> <label> Project title <br /> <input type="text" name="title" /> </label> <label> Target Finish Date <br /> <input type="date" name="due" /> </label> </fieldset> <button type="submit" disabled={busy}> {busy ? "Creating..." : "Create"} </button> </Form> );}

See:

  • useNavigation

Optimistic UI

Knowing the formData being sent to an action is often enough to skip the busy indicators and render the UI in the next state immediately, even if your asynchronous work is still pending. This is called "optimistic UI".

function LikeButton({ tweet }) { const fetcher = useFetcher(); // if there is `formData` then it is posting to the action const liked = fetcher.formData ? // check the formData to be optimistic fetcher.formData.get("liked") === "yes" : // if its not posting to the action, use the record's value tweet.liked; return ( <fetcher.Form method="post" action="toggle-liked"> <button type="submit" name="liked" value={liked ? "yes" : "no"} /> </fetcher.Form> );}

(Yes, HTML buttons can have a name and a value).

While it is more common to do optimistic UI with a fetcher, you can do the same with a normal form using navigation.formData.

Data Fetchers

HTML Forms are the model for mutations but they have one major limitation: you can have only one at a time because a form submission is a navigation.

Most web apps need to allow for multiple mutations to be happening at the same time, like a list of records where each can be independently deleted, marked complete, liked, etc.

Fetchers allow you to interact with the route actions and loaders without causing a navigation in the browser, but still getting all the conventional benefits like error handling, revalidation, interruption handling, and race condition handling.

Imagine a list of tasks:

function Tasks() { const tasks = useLoaderData(); return tasks.map((task) => ( <div> <p>{task.name}</p> <ToggleCompleteButton task={task} /> </div> ));}

Each task can be marked complete independently of the rest, with its own pending state and without causing a navigation with a fetcher:

function ToggleCompleteButton({ task }) { const fetcher = useFetcher(); return ( <fetcher.Form method="post" action="/toggle-complete"> <fieldset disabled={fetcher.state !== "idle"}> <input type="hidden" name="id" value={task.id} /> <input type="hidden" name="status" value={task.complete ? "incomplete" : "complete"} /> <button type="submit"> {task.status === "complete" ? "Mark Incomplete" : "Mark Complete"} </button> </fieldset> </fetcher.Form> );}

See:

  • useFetcher

Race Condition Handling

React Router will cancel stale operations and only commit fresh data automatically.

Any time you have asynchronous UI you have the risk of race conditions: when an async operation starts after but completes before an earlier operation. The result is a user interface that shows the wrong state.

Consider a search field that updates a list as the user types:

?q=ry |---------------| ^ commit wrong state?q=ryan |--------| ^ lose correct state

Even though the query for q?=ryan went out later, it completed earlier. If not handled correctly, the results will briefly be the correct values for ?q=ryan but then flip over the incorrect results for ?q=ry. Throttling and debouncing are not enough (you can still interrupt the requests that get through). You need cancellation.

If you're using React Router's data conventions you avoid this problem completely and automatically.

?q=ry |-----------X ^ cancel wrong state when correct state completes earlier?q=ryan |--------| ^ commit correct state

Not only does React Router handle race conditions for a navigation like this, it also handles it for many other cases like loading results for an autocomplete or performing multiple concurrent mutations with fetcher (and its automatic, concurrent revalidations).

Error Handling

The vast majority of your application errors are handled automatically by React Router. It will catch any errors that are thrown while:

  • rendering
  • loading data
  • updating data

In practice, this is pretty much every error in your app except those thrown in event handlers (<button onClick>) or useEffect. React Router apps tend to have very few of either.

When an error is thrown, instead of rendering the route's element, the errorElement is rendered.

<Route path="/" loader={() => { something.that.throws.an.error(); }} // this will not be rendered element={<HappyPath />} // but this will instead errorElement={<ErrorBoundary />}/>

If a route doesn't have an errorElement, the error will bubble to the nearest parent route with an errorElement:

<Route path="/" element={<HappyPath />} errorElement={<ErrorBoundary />}> {/* Errors here bubble up to the parent route */} <Route path="login" element={<Login />} /></Route>

See:

  • <Route errorElement>
  • useRouteError

React Router will emulate the browser's scroll restoration on navigation, waiting for data to load before scrolling. This ensures the scroll position is restored to the right spot.

You can also customize the behavior by restoring based on something other than locations (like a url pathname) and preventing the scroll from happening on certain links (like tabs in the middle of a page).

See:

  • <ScrollRestoration>

Web Standard APIs

React Router is built on web standard APIs. Loaders and actions receive standard Web Fetch API Request objects and can return Response objects, too. Cancellation is done with Abort Signals, search params are handled with URLSearchParams, and data mutations are handled with HTML Forms.

When you get better at React Router, you get better at the web platform.

Search Params

TODO:

Location State

TODO:

Feature Overview v6.26.2 (2024)
Top Articles
Tenant Insurance Calgary | Free Renters Insurance Quotes
Best Power Tool Brands Ranked in America's Most Trusted Study — Lifestory Research
Craigslist Warren Michigan Free Stuff
My E Chart Elliot
Frank Lloyd Wright, born 150 years ago, still fascinates
St Als Elm Clinic
Polyhaven Hdri
Watch Mashle 2nd Season Anime Free on Gogoanime
Ati Capstone Orientation Video Quiz
Kagtwt
Was sind ACH-Routingnummern? | Stripe
Saw X | Rotten Tomatoes
Jc Post News
Samsung Galaxy S24 Ultra Negru dual-sim, 256 GB, 12 GB RAM - Telefon mobil la pret avantajos - Abonament - In rate | Digi Romania S.A.
Kris Carolla Obituary
The best TV and film to watch this week - A Very Royal Scandal to Tulsa King
Hollywood Bowl Section H
Morristown Daily Record Obituary
Sea To Dallas Google Flights
Conan Exiles Sorcery Guide – How To Learn, Cast & Unlock Spells
Sussyclassroom
Great Clips Grandview Station Marion Reviews
Evil Dead Rise Showtimes Near Pelican Cinemas
Beaufort 72 Hour
Pioneer Library Overdrive
Scott Surratt Salary
Tomb Of The Mask Unblocked Games World
Mawal Gameroom Download
Earthy Fuel Crossword
100 Million Naira In Dollars
Mobile Maher Terminal
Calculator Souo
Grandstand 13 Fenway
Ourhotwifes
Plato's Closet Mansfield Ohio
Why The Boogeyman Is Rated PG-13
Foolproof Module 6 Test Answers
Domina Scarlett Ct
Scottsboro Daily Sentinel Obituaries
Stanford Medicine scientists pinpoint COVID-19 virus’s entry and exit ports inside our noses
Ise-Vm-K9 Eol
craigslist | michigan
Ferguson Employee Pipeline
Fwpd Activity Log
Three V Plymouth
Parent Portal Pat Med
Television Archive News Search Service
R/Gnv
Nearest Wintrust Bank
Image Mate Orange County
Appsanywhere Mst
Ravenna Greataxe
Latest Posts
Article information

Author: Terrell Hackett

Last Updated:

Views: 6764

Rating: 4.1 / 5 (72 voted)

Reviews: 87% of readers found this page helpful

Author information

Name: Terrell Hackett

Birthday: 1992-03-17

Address: Suite 453 459 Gibson Squares, East Adriane, AK 71925-5692

Phone: +21811810803470

Job: Chief Representative

Hobby: Board games, Rock climbing, Ghost hunting, Origami, Kabaddi, Mushroom hunting, Gaming

Introduction: My name is Terrell Hackett, I am a gleaming, brainy, courageous, helpful, healthy, cooperative, graceful person who loves writing and wants to share my knowledge and understanding with you.