Up to this point, we’ve been working with routers that are relatively small. As our application grows and we add more features, we may want additional components to render within our defined views depending on user actions.
For example, suppose that we have an About
page that will be rendered if we hit the path /about
. We’d like to implement a new feature that will display a secret message in About
if the path changes to /about/secret
. We might try and do this:
/* imports ... */ const router = createBrowserRouter(createRoutesFromElement([ <Route path='/about' element={ <About/> }> />, <Route path='/about/secret' element={ <Secret/> }> /> ]));
Since React Router matches paths exactly, if we go to the path /about/secret
, it will only render Secret
and not About
. We’d like to render About
when we hit /about
and also render Secret
below About
when we hit /about/secret
. We can do this using nested routes.
A nested route, as the name suggests, is a Route
within a Route
. A Route
containing one or more Route
s nested within it is known as the parent route and a Route
that is contained within another Route
is known as the child route. When nesting Route
s, the child Route
path
is relative to the parent Route
‘s path
so we shouldn’t include the parent path
in its path
prop.
For example, we can create a nested route by refactoring the code above, like so:
/* imports ... */ const router = createBrowserRouter(createRoutesFromElement( <Route path='/about' element={ <About/> }> {/* About renders if path starts with /about */} <Route path='secret' element={ <Secret/> }> /> {/* we can exclude /about from this path since it is relative to its parent */} </Route> ));
Using this nested route, the About
component will render when the path starts with /about
. If the path matches /about/secret
, the Secret
component will render in addition to the About
component. Remember that a Route
can be both a parent and child route if it is nested within a route and contains nested routes within it. The same parent/child properties would apply.
Our router is configured to render our nested route, but if we ran this code we still wouldn’t see Secret
rendered below About
. That’s because we haven’t told About
where to render its child route element. To do this we need to make use of the React Router Outlet
component in the parent About
component, like so:
import { Outlet } from 'react-router-dom'; // Rendered when the user visits '/about' export default function About() { return ( <main> <h1>Lorem ipsum dolor sit amet.</h1> <Outlet/> {/* renders child element when user visits /about/secret */} <main/> ); }
Now when we visit /about/secret
our router will render About
and its child route component, Secret
, wherever the Outlet
component is defined in the parent. You can think of it as the router replacing Outlet
with our defined child route.
When using nested routes we can also make use of index routes. An index route is a Route
that uses the index
prop instead of a path
prop and is special because it renders on its parent’s path
. For example:
/* imports ... */ const router = createBrowserRouter(createRoutesFromElement( <Route path='/about' element={ <About/> }> {/* About renders if path starts with /about */} <Route index element={ <IndexComponent/> }> /> {/* Will render when the path is /about */} <Route path='secret' element={ <Secret/> }> /> {/* Will render when the path is /about/secret */} </Route> ));
We can think of an index route as a default Route
that will render in its parent’s Outlet
when the path matches the parent path
exactly so there’s some content in that space.
Nested routes give us fine-tuned control over what, when, and where certain elements appear within their parent Route
. Let’s practice what we’ve learned by adding some nested routes to our application.
Instructions
Task 1
In the running application, navigate to the sign-up form and choose a username. Then navigate to the new “Profile” link that will appear. The URL will change to /profile
and we should see the username we just entered, followed by a link to an “Edit” page. Try clicking on this link – we’ll notice that the URL changes, but we get a 404
error.
The EditProfileForm
component should render when the URL changes to /profile/edit
but it is currently not being rendered by the application. Let’s fix that with a nested routing approach.
First, in App.js, let’s define a Profile
child Route
for the path /profile/edit
that should render EditProfileForm
. Note that navigating to /profile/edit
still won’t render correctly (we’ll fix this next).
Hint
Recall that we can define a nested Route
like:
<Route path="/icecream" element={ <Icecream />} > {/* renders when path starts with /icecream */} <Route path="flavors" element={ <Flavors/> } /> {/* renders on path /icecream/flavors*/} </Route>
Notice that child Route
path
prop is relative to parents.
Task 2
When running the application and navigating to /profile/edit
we’ll notice that we get no error (404
) but the EditProfileForm
still isn’t rendering alongside Profile
. Let’s fix this by adding an Outlet
in Profile
. Navigate to Profile.js and import Outlet
from react-router-dom
.
After importing Outlet
, add the Outlet
component under the Link
component. In the running application if we navigate to /profile
we’ll see an “Edit” link which we can click and see the EditProfileForm
render under it. If we fill out the username input and click “Edit” we’ll see the Profile
and EditProfileForm
rendered together.
Hint
Recall that when using nested routes we need to specify where the child route needs to be rendered in the parent route. For example:
/* In App.js */ <Route path="/icecream" element={ <Icecream />} > {/* renders when path starts with /icecream */} <Route path="flavors" element={ <Flavors/> } /> {/* renders on path /icecream/flavors*/} </Route> /* In IceCream.js */ export default function IceCream() { render ( <div> <h1>IceCream<h1/> <Outlet/> {/* Where child route component should be rendered */} </div> ); }
Task 3
In the running application, when we navigate to /categories
we see a list of article categories rendered. When we try to click on one of these links (like html
) notice your path changes to /categories/html
and we get 404
error. If we look at the Categories.js component we’ll notice an Outlet
defined but in App.js we haven’t defined a Route
for the Category
component. Let’s fix that.
In App.js, define a child Route
for the Category
Route
that renders when the path matches a path like /categories/html
or /categories/javascript
.
Hint
Recall that nested routes are relative to their parents so we don't need to include its parent `path` in its `path` prop. We can use nesting with URL params like:
<Route path="/icecream" element={ <Icecream />} > {/* renders when path starts with /icecream */} <Route path=":flavor" element={ <Flavor/> } /> {/* renders on path /icecream/peacan or /icecream/vanilla*/} </Route>