React Router
React Router is a popular library for managing routing in React applications. It enables you to create SPAs by defining routes and rendering components based on the current URL.
Poetically, React Router follows a similar ethos, except instead of UI primitives, they give you routing primitives. To align with React, naturally, these “routing primitives” are really just a collection of React components and Hooks.
Installation
Installation
Understanding Routing Concepts
Before we continue, let’s have a quick understanding of some key concepts in React Router:
- History Stack: React router intelligently uses the HTML5 History API to keep track of the user’s navigation history. When a user visits a new URL, React Router pushes that URL to the history stack. As the user navigates to other URLs, they are also pushed to the stack. This is how React Router can navigate back and forth between routes without refreshing the page.
- Location: This refers to the URL that you’re currently on when navigating through a website. It is the topmost URL in the history stack and how React Router dynamically changes the content displayed to match the correct URL.
Basics
Once you have this library there are three things you need to do in order to use React Router.
- ✅ Setup your router
- ✅ Define your routes
- ✅ Handle navigation
Browser router
Naturally, in order to do its thing, React Router needs to be both aware and in control of your app’s location. The way it does this is with its BrowserRouter
component.
Unser The hood
Under the hood, BrowserRouter uses both the history library as well as React Context. The history library helps React Router keep track of the browsing history of the application using the browser’s built-in history stack, and React Context helps make history available wherever React Router needs it.
There's not much to
BrowserRouter
, you just need to make sure that if you're using React Router on the web, you wrap your app inside of theBrowserRouter
component.
Configuring The Router
This is the recommended router for all React Router web projects. It uses the DOM History API to update the URL and manage the history stack. It also enables the v6.4 data APIs like loaders, actions, fetchers and more.
type declaration
Route
An array of Route
objects with nested routes on the children
property.
basename
The basename of the app for situations where you can’t deploy to the root of the domain, but a sub directory.
The trailing slash will be respected when linking to the root:
future
An optional set of Future Flags to enable for this Router. We recommend opting into newly released future flags sooner rather than later to ease your eventual migration to v7.
window
Useful for environments like browser devtool plugins or testing to use a different window than the global window
.
Route(s)
Routes are perhaps the most important part of a React Router app. They couple URL segments to components, data loading and data mutations. Through route nesting, complex application layouts and data dependencies become simple and declarative.
Routes are objects passed to the router creation functions:
You can also declare your routes with JSX and
createRoutesFromElements
, the props to the element are identical to the properties of the route objects:
When using
RouterProvider
, if you do not wish to specify a React element (i.e.,element={<MyComponent />}
) you may specify aComponent
instead (i.e.,Component={MyComponent}
) and React Router will callcreateElement
for you internally.You should only do this for
RouterProvider
applications though since usingComponent
inside of<Routes>
will de-optimize React’s ability to reuse the created element across renders.
Type Declaration
Deep dive
path
The path pattern to match against the URL to determine if this route matches a URL, link href, or form action.
URL Segments
Dynamic Segment
If a path segment starts with :
then it becomes a “dynamic segment”. When the route matches the URL, the dynamic segment will be parsed from the URL and provided as params
to other router APIs.
You can have multiple dynamic segments in one route path:
Dynamic segments cannot be "partial":
- 🚫
"/teams-:teamId"
- ✅
"/teams/:teamId"
- 🚫
"/:category--:productId"
- ✅
"/:productSlug"
Optional segments
You can make a route segment optional by adding a ?
to the end of the segment.
You can have optional static segments, too:
Splats
Also known as “catchall” and “star” segments. If a route path pattern ends with /*
then it will match any characters following the /
, including other /
characters.
You can destructure the
*
, you just have to assign it a new name. A common name issplat
:
Layout Routes
Omitting the path makes this route a "layout route". It participates in UI nesting, but it does not add any segments to the URL.
In this example, <h1>Layout</h1>
will be rendered along with each child route’s element
prop, via the layout route’s Outlet.
Outlet
An <Outlet>
should be used in parent route elements to render their child route elements. This allows nested UI to show up when child routes are rendered. If the parent route matched exactly, it will render a child index route or nothing if there is no index route.
Index
Determines if the route is an index route. Index routes render into their parent’s Outlet at their parent’s URL (like a default child route).
hildren
caseSensitive Route
Instructs the route to match case or not:
- Will match
"wEll-aCtuA11y"
- Will not match
"well-actua11y"
loader
Each route can define a “loader” function to provide data to the route element before it renders.
This feature only works if using a data router, see Picking a Router
If you are not using a data router like createBrowserRouter
, this will do nothing
As the user navigates around the app, the loaders for the next matching branch of routes will be called in parallel and their data made available to components through useLoaderData
.
params
Route params are parsed from dynamic segments and passed to your loader. This is useful for figuring out which resource to load:
Note that the
:teamId
in the path is parsed as provided asparams.teamId
by the same name.
request
This is a Fetch Request instance being made to your application.
function loader({ request }) {}
The most common use case is creating a URL and reading the URLSearchParams from it:
Note that the APIs here are not React Router specific, but rather standard web objects: Request, URL, URLSearchParams.
Returning Responses
While you can return anything you want from a loader and get access to it from useLoaderData
, you can also return a web Response.
This might not seem immediately useful, but consider fetch
. Since the return value of fetch
is a Response, and loaders understand responses, many loaders can return a simple fetch!
You can construct the response yourself as well:
React Router will automatically call response.json()
so your components don’t need to parse it while rendering:
Using the json
utility simplifies this so you don’t have to construct them yourself. This next example is effectively the same as the previous example:
If you’re planning an upgrade to Remix, returning responses from every loader will make the migration smoother.
Towing in Loaders
You can throw
in your loader to break out of the current call stack (stop running the current code) and React Router will start over down the “error path”.
function loader({ request, params }) {
const res = await fetch(`/api/properties/${params.id}`);
if (res.status === 404) {
throw new Response("Not Found", { status: 404 });
}
return res.json();
}
Basic routing
React Router provides components like BrowserRouter
, Route
, and Link
to set up basic routing. Here’s a simple example:
In this example:
-
BrowserRouter
sets up the router. -
Link
is used for navigation. -
Route
specifies which component to render based on the URL.
Nested Routing <a name="nested-routing"></a>
Nested routing allows you to create complex layouts with multiple nested components. You can nest Route
components within other components:
Programmatic Navigation <a name="programmatic-navigation"></a>
Sometimes, you need to navigate programmatically, e.g., after a form submission. You can use the useHistory hook for this:
URL parameter
- useParams()
- useSearchParams()
- called in event handlers or effect
- useLocation()
Route Parameters
You can capture dynamic values from the URL using route parameters. For instance, to create a profile page with a dynamic username
Access the parameter in your component using useParams
hook:
NavLink & Link
React Router has a component called NavLink
. It is similar to Link
but is mainly used when dealing with menu navigation links, unlike the Link
component, which can be used for any type of link.
The major difference between NavLink
and Link
is that NavLink
can detect when it is in an active state. When NavLink
detects that it is active, it adds an active
class to its component by default.
Since navigation menus are present in the history website, let’s update the Link
component to NavLink
:
Overall,
NavLink
is a more robust option than theLink
component when creating links for navigation menus.
Basic routing
React Router provides components like BrowserRouter
, Route
, and Link
to set up basic routing. Here’s a simple example:
In this example:
-
BrowserRouter
sets up the router. -
Link
is used for navigation. -
Route
specifies which component to render based on the URL.
Route Parameters
You can capture dynamic values from the URL using route parameters. For instance, to create a profile page with a dynamic username
Access the parameter in your component using useParams
hook:
Nested Routing <a name="nested-routing"></a>
Nested routing allows you to create complex layouts with multiple nested components. You can nest Route
components within other components:
Programmatic Navigation <a name="programmatic-navigation"></a>
Sometimes, you need to navigate programmatically, e.g., after a form submission. You can use the useHistory hook for this:
Optimistic Ui
Optimistic UI is a pattern that you can use to simulate the results of a mutation and update the UI even before receiving a response from the server. Once the response is received from the server, the optimistic result is thrown away and replaced with the actual result.
Optimistic UI provides an easy way to make your UI respond much faster, while ensuring that the data becomes consistent with the actual response when it arrives.
Basic optimistic UI
Let’s say we have an “edit comment” mutation, and we want the UI to update immediately when the user submits the mutation, instead of waiting for the server response. This is what the optimisticResponse
parameter to the mutate
function provides.
The main way to get GraphQL data into your UI components with Apollo is to use a query, so if we want our optimistic response to update the UI, we have to make sure to return an optimistic response that will update the correct query result.
Loader
What is it ?
Loaders are a new feature in React Router v6 that allow you to load data for a route before it renders. This can be useful for fetching data from APIs, databases, or other external sources.
How does it work?
To use a loader, you simply add a loader prop to your route definition. The loader prop should be a function that returns a Promise. The Promise will be resolved with the data that you want to load for the route.
- Export a loader function from the page that fetches the data that page will need
- Pass loader prop to the route that renders that page and pass in the loader function
- Use the useLoaderData hook in the component to get the data.
Once the loader function has resolved, the data will be made available to the route element through the useLoaderData
hook. You can then use the data in your route element as needed.
MUST ORDER
Navigate component
The
Use Cases
Basic Usage
The Navigate
component is typically used within your route configuration to specify where to navigate when a particular route is matched. It can be employed within the <Route>
component to control the routing behavior. Here’s a basic example:
In this example, if no matching route is found, the
<Navigate to="/" />
component will redirect users to the home page.
Redirects
You can use <Navigate />
to redirect users from one route to another, such as after a form submission.
Conditional Navigation
Conditionally navigate users based on certain criteria, like user authentication.
Replace prop with navigation
The
replace
prop, as shown in the example above, allows you to replace the current history entry when navigating. This means that the user won't be able to use the browser's back button to return to the previous page. It can be useful for scenarios where you want to prevent navigation history from being exposed.
Opinions
- 🔥 Opinion:
- ➡️ The
Navigate
component is a clean and declarative way to handle navigation within your application, making your routing logic more self-explanatory and maintainable.
- ➡️ The
Gotcha
- 🔥 Gotcha:
- ➡️ Ensure that the
Navigate
component is placed within a<Routes>
component and that it is conditionally used within a route’selement
prop. Placing it outside of the routing context may not produce the desired navigation behavior.
- ➡️ Ensure that the
Use Navigation (useNavigation)
This hook tells you everything you need to know about a page navigation to build pending navigation indicators and optimistic UI on data mutations. Things like:
- ➡️ Global loading indicators
- ➡️ Disabling forms while a mutation is happening
- ➡️ Adding busy indicators to submit buttons
- ➡️ Optimistically showing a new record while it’s being created on the server
- ➡️ Optimistically showing the new state of a record while it’s being updated
This feature only works if using a data router
navigation.state
- 🔥 The navigation state can be in one of the following state.
- ➡️ idle - There is no navigation pending.
- ➡️ submitting - A route action is being called due to a form submission using POST, PUT, PATCH, or DELETE
- ➡️ loading - The loaders for the next routes are being called to render the next page
Normal navigations and GET form submissions transition through these states:
idle → loading → idle
Form submissions with POST, PUT, PATCH, or DELETE transition through these states:
idle → submitting → loading → idle
navigation.formData
Any POST, PUT, PATCH, or DELETE navigation that started from a <Form>
or useSubmit
will have your form’s submission data attached to it. This is primarily useful to build “Optimistic UI” with the submission.formData
FormData
object.
In the case of a GET form submission,
formData
will be empty and the data will be reflected innavigation.location.search
.
navigation.location
This tells you what the next location is going to be.
Note that this link will not appear “pending” if a form is being submitted to the URL the link points to, because we only do this for “loading” states. The form will contain the pending UI for when the state is “submitting”, once the action is complete, then the link will go pending.