Tanner Linsley the creator of Tanstack Query (previous react-query) and other such popular react libraries just released the v1 of the Tanstack router and it's been getting some hype ever since. So one might ask what is this new router ? and why do we even need a router in the age of meta frameworks which comes with a routing solution already. As per the official docs Tanstack router is "A fully type-safe React router with built-in data fetching, stale-while revalidate caching and first-class search-param APIs." Tanstack router could primarily be used for single page applications (p.s. they are not going anywhere) but we can use it as for SSR as well (more on than in later posts). Let's get started.
Installation.
Before we begin let's install it, you can any package manager of your choice, I'll go with yarn.
yarn add @tanstack/react-router
Getting Started
Routes are the most important part of Tanstack router and they can be created with RootRoute
and Route
classes. We can nest multiple routes inside each other to create a route tree.
Root Route
Every route tree should begin with an root route and can be created as
import { RootRoute } from '@tanstack/react-router';
const rootRoute = new RootRoute({
component: RootComponent,
});
RootRoute
class optionally accepts a object in which we can define the component we want to render for the route.
Now that we have created our root route we would need to add an outlet.
The Oulet
component is used to render child routes and can be rendered anywhere in our component tree. We'll render it in our root component as below.
import { Outlet } from '@tanstack/router';
function RootComponent() {
return (
<div>
<div className="p-2 flex gap-2">
<Link to="/" className="[&.active]:font-bold">
Home
</Link>{" "}
<Link to="/about" className="[&.active]:font-bold">
About
</Link>
</div>
<hr />
<Outlet />
</div>
);
}
As you may see, we have a navbar in our root component with following links /
and about
, But if you were to click on them nothing will render (we will come to 404 routes later in the post) and we are using the built in Link
component from Tanstack router.
Creating Routes
We have setup our root route and an outlet, now it's time to create some child routes. We will create a index route /
and /about
routes. Route can be created with the Route
class.
import { Route } from '@tanstack/router';
const indexRoute = new Route({
path: '/',
component: function Home() {
return (
<div className="p-2">
<h3>Tanstack router example</h3>
</div>
);
},
getParentRoute: () => rootRoute,
});
const aboutRoute = new Route({
path: '/about',
component: function About() {
return <div className="p-2">Hello from About!</div>;
},
getParentRoute: () => rootRoute,
});
We pass a configuration object to the Route
specifying the path and the component to render when this path is matched and then we specify the parent route for this route and we do that with the getParenRoute
.
Creating a route tree and setting up our router.
Now that we have created our routes it's time to create our route tree. To create a route tree, we need to add all the children routes to our root route as below.
const routeTree = rootRoute.addChildren([indexRoute, aboutRoute]);
Now we need to create a router with our route tree, which can be created using the Router
class from tanstack router
import { Router } from '@tanstack/router';
const router = new Router({
routeTree
});
We created our router instance using the Router
class and passed our routeTree
to it. Now we need to pass our router instance to the RouteProvider
to complete our routing setup.
import { RouteProvider } from '@tanstack/router';
<RouteProvider
router={router}
/>
Now if you click on the links in the navbar, it will render our route correctly.
Dynamic Routes
So far we have only looked at how to create static routes, but you may wonder what about dynamic routes. Dynamic routes are also created with Route
class and their path starts with $
symbol followed by the label. We can acess dyanmic segments using the useParams
hook throughout our app.
const blogRoute = new Route({
path: '/blog',
component: Blog
getParentRoute: () => rootRoute,
});
const blogPostRoute = new Route({
path: '$blogId'
component: BlogPost,
getParentRoute: () => blogPostRoute,
});
const routeTree = rootRoute.addChildren([blogRoute.addChildren([blogPostRoute])]);
For eg: For route /blog/1
, will produce a param of blogId
with value 1
.
Navigation
Link Component
Tanstack router provides a Link
component for navigating between routes. All we have to do is pass the path of the route to the Link
component's to
prop.
import { Link } from '@tanstack/router';
<Link to="/about">About Me</Link>
We can also use the same component for navigating to dynamic routes like below.
<Link
to="/users/$userId"
params={{
userId: 1,
}}
>
Prateek
</Link>
We can also pass query params to our link component like below
<Link
search={{
filter: 'prateek' ,
}}
>
Results
</Link>
Link
component provides support two more props as activeProps
and inactiveProps
for styling the active and inactive states of our link. These props accepts object with css styles or css classes. Here's how we can use these props to style our link.
// styling with css styles object.
<Link
to="/"
activeProps={{
style: {
background: 'blue',
color: '#fff',
}
}}
>
Home
</Link>
// styling with css classes.
<Link
to="/"
activeProps={{
classes: 'text-white bg-blue-600'
}}
>
Home
</Link>
useNavigate
Tanstack router also provides a useNavigate
to navigate to ours routes imperatively. The useNavigate
hooks returns a navigate function which is used for navigation.
const navigate = useNavigate();
async function login() {
try {
await loginMutation({
email,
password,
}
navigate({to: "/home"});
} catch (error) {
console.error(error);
}
}
we can also pass an optional params object with dynamic path segments to the navigate
function.
Navigate Component
Another way we could navigate to a route is with the Navigate
component, it allows to us redirect when a component mounts or when a certain condition is met. One example of this is when we want to redirect the to a login page if the user is not logged in.
Rest of the component code ...
if (notAuthenticated) {
return <Navigate to="/login" />;
}
Rest of the component code ...
Going Deeper
Next we'll move to data fetching, Tanstack router provides loaders inspired from Remix's loaders, they allow us to fetch data for a route. For this we'll create couple of routes as /users
and /users/$id
and will fetch some dummy users using the json placeholder api.
// api services...
export async function fetchUsers() {
cons res = await fetch('https://jsonplaceholder.typicode.com/users');
if (res.ok) {
return await res.json();
}
return [];
}
export async function fetchUserById(userId: number) {
const res = await fetch(`https://jsonplaceholder.typicode.com/users/${userId}`);
if (res.ok) {
return await res.json();
}
return null;
}
We'll create our routes like this. We'll first create the /users
route.
const usersRoute = new Route({
getParentRoute: () => rootRoute,
path: '/users',
loaders: () => fetchUsers(),
component: Users,
});
export function Users() {
const users = usersRoute.useLoaderData();
return (
<div>
{users.length === 0 && <span>No users found</span>}
{users.length > 0 && (
<ul className="space-y-3">
{users.map((user) => (
<li>
<Link to="/users/$userId" params={{userId: user.id}}>
<span>{user.name}</span>
</Link>
</li>
))}
</ul>
)}
</div>
);
}
One thing we did different from our previous route(s) declaration is that, we defined a loader option and passed in the result of our fetchUsers
function. Now we can access our data in our component with useLoaderData
utility available on our route instance.
Now we'll create our user detail route and pass a function which returns our user data to the loader option, since we need an id to fetch a user, we can read it from the userId (because we have defined our route as $userId
) property available on the params object passed to our loader function. We can also read the param value from the useParams
method available on the route instance.
const userRoute = new Route({
getParentRoute: () => rootRoute,
path: '/users/$userId',
loaders: ({ params }) => fetchUsersById(+params.userId),
component: Users,
});
export function Users() {
const user = userRoute.useLoaderData();
const { userId } = userRoute.useParams();
console.log('userId - ', userId);
return (
<div>
{user ? (
<div>
Name - {user.name}
UserName - {user.username}
Email - {user.email}
Website - {user.website}
</div>
) : 'User not found'}
</div>
)
}
Creating our route tree and setting up our router.
const routeTree = rootRoute.addChildren([
usersRoute,
userRoute,
]);
const router = new Router({
routeTree,
})
Now if we navigate to the /users
route, Users
component is rendered and if we scroll further down, Tanstack router will preload all unique $userId
route path along with the data defined in the loader (preloading is enabled by default in Tanstack router), so that if we navigate to any user's page, our page will load instantly.
Pathless Routes
Pathless routes are routes that don't have a path but an id instead to uniquely identify them. Pathless routes are useful in following cases
- Using a shared layout for all child routes
- Common data dependency
- Sharing context with child routes.
- Provide global error and pending component for the route subtree.
NotFoundRoute
Routes which don't match any path segments or dynamic path segments are caught by splat routes or NotFoundRoute(s). We can place a NotFoundRoute
at every root/parent routes but if we have many subtrees it could become tedious, instead we can create a global NotFoundRoute and pass it to our routeTree instead. Here'an example of a NotFoundRoute:-
const globalNotFoundRoute = new NotFoundRoute({
getParentRoute: () => rootRoute,
component: () => {
return <span>Page not found</span>
},
});
const router = new Route({
routeTree,
notFoundRoute: globalNotFoundRoute,
});
Conclusion
We have at looked some of the basics and important features of Tanstack router but there still so much more to explore. Tanstack router is a feature packed library with some very notable features like file-based routing, SSR support, code-splitting, full type safety and much more and it has the potential to be the de-facto router for single page applications not just for react.
I am planning to write another blog posts covering some advanced use-cases like search params based querying, filtering and pagination.
You can find the source code for the blog below. github