Check your version
This post assumes you're using React Router v6. If not, find your version below.

React Router has gone through a few different iterations over the years. Though the current API (v6) takes a declarative, component-based, <Route /> as you go approach – this hasn’t always been the case.

In fact, in the first versions of React Router (v1-v3), instead of composing your Routes as you do now throughout your application, you’d declare them up front in a central route config then pass that to ReactDOM.render.

const routes = (
<Router>
<Route path='/' component={Main}>
<IndexRoute component={Home} />
<Route path='battle' component={ConfirmBattle} />
<Route path='results' component={Results} />
</Route>
</Router>
)
ReactDOM.render(routes, document.getElementById('app'))

Though React Router has moved away from this central route config approach, it still had its benefits. Namely, when server rendering or doing static analysis.

The good news is, as of v6, React Router now comes with a useRoutes Hook that makes collocating your routes into a central route config not only possible, but simple with a first class API as well.

Say we had the following paths in our application.

/
/invoices
:id
pending
complete
/users
:id
settings

Typically if you wanted to map those paths to different React components, you’d render something like this.

return (
<Routes>
<Route path='/' element={<Home />} />
<Route path='/invoices' element={<Invoices />}>
<Route path=':id' element={<Invoice />} />
<Route path='pending' element={<Pending />} />
<Route path='complete' element={<Complete />} />
</Route>
<Route path='/users/*' element={<Users />} />
</Routes>
)

Notice that we’re rendering the nested routes for invoices/:id, invoices/pending, and invoices/complete here but the nested routes for /users/:id and /users/settings are going to be rendered inside the Users component.

Now what useRoutes allows us to do is, instead of declaring our routes using React elements, we can do it using JavaScript objects all in one location.

useRoutes takes in an array of JavaScript objects which represent the routes in your application. Similar to the React element API with <Route>, each route has a path, element, and an optional children property.

import { useRoutes } from 'react-router-dom'
const routes = useRoutes([
{ path: '/', element: <Home /> },
{
path: '/invoices',
element: <Invoices />,
children: [
{ path: ':id', element: <Invoice /> },
{ path: '/pending', element: <Pending /> },
{ path: '/complete', element: <Complete /> },
]
},
{
path: '/users',
element: <Users />,
children: [
{ path: ':id', element: <Profile /> },
{ path: '/settings', element: <Settings /> },
]
}
])
export default function App () {
return (
<div>
<Navbar />
{routes}
</div>
)
}

What makes useRoutes even more interesting is how React Router uses it internally. In fact, when you use the React element API to create your Routes, it’s really just a wrapper around useRoutes.

Want to learn more?
If you liked this post and want to learn more, check out our free Comprehensive Guide to React Router.

Before you leave

I know, "another newsletter pitch" - but hear me out. Most JavaScript newsletters are terrible. When's the last time you actually looked forward to getting one? Even worse, when's the last time you actually read one rather than just skim it?

We wanted to change that, which is why we created Bytes. The goal was to create a JavaScript newsletter that was both educational and entertaining. 85,204 subscribers and an almost 50% weekly open rate later, it looks like we did it.

Delivered to 85,204 developers every Monday

Avatar for Tyler McGinnis

Tyler McGinnis

CEO of ui.dev. Obsessed with teaching, writing, swimming, biking, and running.

Share this post

Want more react-router?

This is part of our React Router course. You can take the rest of the course by starting a free 3-day trial.