useRoutes
Hook version of <Routes> that uses objects instead of components. These objects have the same properties as the component props. The return value of useRoutes is either a valid React element you can use to render the route tree, or null if nothing matched.
Signature
function useRoutes(
routes: RouteObject[],
locationArg?: Partial<Location> | string
): React.ReactElement | null
interface RouteObject {
path?: string;
index?: boolean;
element?: React.ReactNode;
Component?: React.ComponentType;
children?: RouteObject[];
caseSensitive?: boolean;
loader?: LoaderFunction;
action?: ActionFunction;
errorElement?: React.ReactNode;
ErrorBoundary?: React.ComponentType;
handle?: unknown;
}
Parameters
An array of route objects that define the route hierarchy.
locationArg
Partial<Location> | string
An optional location object or pathname string to use instead of the current location. Useful for testing or SSR.
Returns
element
React.ReactElement | null
A React element to render the matched route, or null if no routes matched.
Usage
Basic usage
import { useRoutes } from "react-router";
function App() {
const element = useRoutes([
{
path: "/",
element: <Dashboard />,
children: [
{
path: "messages",
element: <Messages />,
},
{
path: "tasks",
element: <Tasks />,
},
],
},
{
path: "team",
element: <Team />,
},
]);
return element;
}
This is equivalent to:
function App() {
return (
<Routes>
<Route path="/" element={<Dashboard />}>
<Route path="messages" element={<Messages />} />
<Route path="tasks" element={<Tasks />} />
</Route>
<Route path="team" element={<Team />} />
</Routes>
);
}
With data loading
const routes = [
{
path: "/",
element: <Layout />,
loader: rootLoader,
children: [
{
index: true,
element: <Home />,
},
{
path: "posts/:id",
element: <Post />,
loader: postLoader,
},
],
},
];
function App() {
return useRoutes(routes);
}
Dynamic routes
function App({ user }) {
const routes = [
{
path: "/",
element: <Layout />,
children: user.isAdmin
? [
{ path: "dashboard", element: <Dashboard /> },
{ path: "admin", element: <Admin /> },
]
: [
{ path: "dashboard", element: <Dashboard /> },
],
},
];
return useRoutes(routes);
}
With custom location
function App() {
const element = useRoutes(
[
{ path: "/", element: <Home /> },
{ path: "/about", element: <About /> },
],
"/about" // Override location
);
// Always renders <About /> regardless of actual URL
return element;
}
Error boundaries
const routes = [
{
path: "/",
element: <Layout />,
errorElement: <ErrorBoundary />,
children: [
{
path: "posts/:id",
element: <Post />,
loader: postLoader,
errorElement: <PostError />,
},
],
},
];
function App() {
return useRoutes(routes);
}
Common Patterns
Route configuration file
// routes.ts
export const routes = [
{
path: "/",
element: <Root />,
loader: rootLoader,
children: [
{
index: true,
element: <Home />,
},
{
path: "about",
element: <About />,
},
],
},
];
// App.tsx
import { routes } from "./routes";
function App() {
return useRoutes(routes);
}
Nested routing
const adminRoutes = [
{ path: "users", element: <Users /> },
{ path: "settings", element: <Settings /> },
];
const routes = [
{
path: "/",
element: <Layout />,
children: [
{ index: true, element: <Home /> },
{
path: "admin",
element: <AdminLayout />,
children: adminRoutes,
},
],
},
];
function App() {
return useRoutes(routes);
}
Lazy loading routes
const routes = [
{
path: "/",
element: <Layout />,
children: [
{
path: "dashboard",
lazy: async () => {
const { Dashboard, loader } = await import("./Dashboard");
return { element: <Dashboard />, loader };
},
},
],
},
];
Protected routes
function App({ user }) {
const routes = [
{
path: "/",
element: <Layout />,
children: [
{ index: true, element: <Home /> },
...(user
? [
{ path: "profile", element: <Profile /> },
{ path: "settings", element: <Settings /> },
]
: [
{ path: "login", element: <Login /> },
]),
],
},
];
return useRoutes(routes);
}
Index routes
const routes = [
{
path: "/",
element: <Layout />,
children: [
{
index: true,
element: <Home />,
},
{
path: "posts",
element: <PostsLayout />,
children: [
{
index: true,
element: <PostsList />,
},
{
path: ":id",
element: <Post />,
},
],
},
],
},
];
Wildcard routes
const routes = [
{
path: "/",
element: <Layout />,
children: [
{ path: "home", element: <Home /> },
{ path: "about", element: <About /> },
{ path: "*", element: <NotFound /> },
],
},
];
Route handles
const routes = [
{
path: "/",
element: <Layout />,
handle: { breadcrumb: "Home" },
children: [
{
path: "posts/:id",
element: <Post />,
loader: postLoader,
handle: {
breadcrumb: (match) => match.data.post.title,
},
},
],
},
];
Comparison with Routes
| Feature | <Routes> | useRoutes |
|---|
| Syntax | JSX components | JavaScript objects |
| Dynamic routes | Harder | Easier |
| Type safety | Less | More (with TypeScript) |
| Code splitting | Manual | Easier with lazy |
| Configuration | Inline | Can be external |
Type Safety
Typed routes
import type { RouteObject } from "react-router";
const routes: RouteObject[] = [
{
path: "/",
element: <Home />,
children: [
{
path: "about",
element: <About />,
},
],
},
];
function App() {
return useRoutes(routes);
}
Custom route type
interface AppRoute extends RouteObject {
title?: string;
permission?: string;
}
const routes: AppRoute[] = [
{
path: "/",
element: <Home />,
title: "Home",
},
{
path: "/admin",
element: <Admin />,
title: "Admin",
permission: "admin",
},
];
Important Notes
Must be inside Router
useRoutes must be called within a <Router> component:
// ❌ Will throw error
function App() {
const element = useRoutes([...]);
return element;
}
// ✅ Correct
function App() {
return (
<BrowserRouter>
<AppRoutes />
</BrowserRouter>
);
}
function AppRoutes() {
const element = useRoutes([...]);
return element;
}
Location override
When overriding location, all parent routes must match:
// Parent route at /app
function ParentRoute() {
// ❌ Won't work - location must start with /app
const element = useRoutes(routes, "/other");
// ✅ Works
const element = useRoutes(routes, "/app/nested");
return element;
}