Nested Routes
We need them if we want to control a part of the user interface by a part of the URL,
for instance in http:localhost:3000/app/cities, the cities part defines a
specific component to be displayed.
Nested routes are defined inside <Route> elements. For the above example:
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Homepage />} />
<Route path="product" element={<Product />} />
<Route path="pricing" element={<Pricing />} />
<Route path="login" element={<Login />} />
<Route path="*" element={<PageNotFound />} />
<Route path="app" element={<AppLayout />} />
</Routes>
</BrowserRouter>
);
}
changes to
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Homepage />} />
<Route path="product" element={<Product />} />
<Route path="pricing" element={<Pricing />} />
<Route path="login" element={<Login />} />
<Route path="*" element={<PageNotFound />} />
<Route path="app" element={<AppLayout />}>
<Route path="cities" element={<p>List of cities</p>} />
</Route>
</Routes>
</BrowserRouter>
);
}
Note, that the element property of the nested route can be any JSX (here for now:
<p>List of cities</p>) or a component.
Additional nested routes are possible:
<Route path="app" element={<AppLayout />}>
<Route path="cities" element={<p>List of cities</p>} />
<Route path="countries" element={<p>Countries</p>} />
<Route path="form" element={<p>Form</p>} />
</Route>
A nested route builds up upon its enclosing Route (here: "app"), so it does only
need the additional path (e.g. cities", not the entire path (e.g. "app/cities").
But where to display the actual component element of nested routes? These are defined
inside the enclosing component using the <Outlet> element.
For instance, the <Cities> element is supposed to be rendered inside the <Sidebar>
element (if the URL shows app/cities). This is done via the <Outlet> element:
function Sidebar() {
return (
<div className={styles.sidebar}>
<Logo />
<AppNav />
<Outlet />
<Footer />
</div>
);
}
export default Sidebar;
When navigating to
app/citiesnow, the defined element (here:<p>List of cities</p>)app/formnow, the defined element (here:<p>Form</p>)
is displayed.
The <Outlet /> element defines where the element, that is associated
with the routing of the URL is supposed to be inserted.
Index Route
Additionally, there is the Index Route. It defines the route to take, if the
URL matches none of the other defines <Route> elements. It is defined like this:
<Route path="app" element={<AppLayout />}>
<Route index element={<p>List of cities</p>} />
<Route path="cities" element={<p>List of cities</p>} />
<Route path="countries" element={<p>Countries</p>} />
<Route path="form" element={<p>Form</p>} />
</Route>
When navigating just to /app now, it displays the defined element at the
position of the <Outlet /> element. If this were not defined, nothing would
be rendered at the <Outlet /> element’s position.
In fact, we can transform any path="/" to index:
<Routes>
<Route path="/" element={<Homepage />} />
<Route path="product" element={<Product />} />
<Route path="pricing" element={<Pricing />} />
<Route path="login" element={<Login />} />
<Route path="*" element={<PageNotFound />} />
<Route path="app" element={<AppLayout />}>
<Route index element={<p>List of cities</p>} />
<Route path="cities" element={<p>List of cities</p>} />
<Route path="countries" element={<p>Countries</p>} />
<Route path="form" element={<p>Form</p>} />
</Route>
</Routes>
becomes
<Routes>
<Route index element={<Homepage />} />
<Route path="product" element={<Product />} />
<Route path="pricing" element={<Pricing />} />
<Route path="login" element={<Login />} />
<Route path="*" element={<PageNotFound />} />
<Route path="app" element={<AppLayout />}>
<Route index element={<p>List of cities</p>} />
<Route path="cities" element={<p>List of cities</p>} />
<Route path="countries" element={<p>Countries</p>} />
<Route path="form" element={<p>Form</p>} />
</Route>
</Routes>
To conclude, we can now define Links to those routes inside the AppNav component.
Here, we only define the relative path, for which the component must only be placed
inside components, which are associated with a compatible URL. For example, when defining
export default function AppNav() {
return (
<nav className={styles.nav}>
<ul>
<li>
<NavLink to="cities">Cities</NavLink>
</li>
<li>
<NavLink to="countries">Countries</NavLink>
</li>
</ul>
</nav>
);
}
adding the component inside Sidebar navigates to defined routes, where defining
it someplace else like inside Homepage navigates to undefined routes.
Homepage /
└── AppNav --> WON'T WORK! Routes to /cities and /countries
└── App /app
└── Sidebar
└── Outlet reacts to /app/cities or /app/countries
└── AppNav --> THIS WORKS! Routes to /app/cities and /app/countries
This is similar to creating a Tab component, but there we had to store the current
state using useState to manage the currently opened and displayed tab content.
Here, we are storing that information inside the URL.