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.