@tanstack/react-router

The typesafe router

#tanstack#router#typescript#react-query

TanStack gets bigger

Once again, Tanner Linsley and his team are releasing a new library accompanied by a promotional video..

I was so hyped when I saw this. I'm a devoted fan of Tanner's team and their work, especially @tanstack/react-query, so I had to give it a try.

File based routing

The first feature that caught my attention was the file-based routing. While code-based routing is also possible, the library recommends against it. Personally, I'm not a huge fan of the directory-centric approach in Next.js, so the flat routing structure here appealed to me. After experimenting with it, I realized that embedding data fetching, suspense fallback component , and error handling within route declarations could make code-based routing somewhat challenging to maintain.

Let's see those file conventions :

The existence of .lazy.tsx files is due to the library not yet handling code extraction automatically.You have to split your code manually between "Critical" (path parsing, search param validation, loaders, ...) and non-critical ones (route components)

So, for a route with a loader, it looks like this :

routes/blog.post.tsx

import { createFileRoute } from "@tanstack/react-router";
import { getPost } from "../api/posts";

export const Route = createFileRoute("/blog/post")({
  loader: async () => {
    return await getPost();
  },
});

routes/blog.post.lazy.tsx

import { createLazyFileRoute } from "@tanstack/react-router";

export const Route = createLazyFileRoute("/blog/post")({
  component: Posts,
});

function Posts() {
  // ...
}

Yeah..., kind of weird to have two files for a single route. It is for sure different than other lib but I did not have looked at it in the long term. Second, it is v1 so maybe, one day, we would have code-extraction to colocate loaders and components.

Type safety

What about type safety? Well, it's as robust as ever with Tanner's team.

Here is a Link component: Take for example the Link component.

Search parameters can be validated with zod and thus be type-safe, as is imperative navigation!

It's incredibly reassuring to know that you can't go wrong with links, search parameters, dynamic routes, and more.

Data fetching

The library is also handling data fetching at route level. While react-router has been doing that since v6, it doesn't do it in a type-safe manner. Loaders offer an efficient means of fetching data, as all nested route loaders are invoked in parallel. As stated in the documentation, "The router is the best place to coordinate these async dependencies, as it's usually the only place in your app that knows where users are headed before content is rendered."

So, let's see how it looks like :

routes/characters.$id.tsx
routes/characters.$id.lazy.tsx

And guess what ? The return of useLoaderData matches the expected type:

In addition to the features offered by react-router, this library provides caching. Here are the pros and cons extracted from the documentation: TanStack Router Cache Pros:

TanStack Router Cache Cons:

It is much than decent but if you think you need something more robust, let's add to the stack tanstack/query !

Perfect fit with React Query

I am a big fan of react-query and I am so happy to see that @tanstack/react-router is a perfect fit with it.

First, I declare my queries (Please read this cool (as always) blog post by TkDodo : The Query Options API):

Then, I use them in my loader:

ensureQueryData will either return the data is already in the cache or fetch it.

Finally, I can use the useQuery hook in my component (specifically the useSuspenseQuery hook to avoid managing loading and error states):

Viewing it in this light may seem a bit verbose for no apparent gain, but it brings some benefits:

Private routes

One common requirements is managing private sections of the app.

Here's what I've found, though I'm not entirely sure it's the best approach:

routes/_private.tsx

import { createFileRoute, redirect } from "@tanstack/react-router";
import { meQueryOptions } from "../queries/users";

export const Route = createFileRoute("/_private")({
  beforeLoad: async ({ context }) => {
    const me = context.queryClient.ensureQueryData(meQueryOptions);

    if (!me)
      throw redirect({
        to: "/",
        search: {
          redirect: location.href,
        },
      });
  },
});

So, it is a layout route where in the beforeLoad function, I check if the user is currently logged in. If not, I redirect to the home page with a redirect function. The search object is used to pass the current location to the home page so it can redirect back to the private page after the user logs in.

Just need to prefix all private routes with _private and you are all set. (_private.profile.tsx)

Conclusion

TanStack Router looks amazing. It is a bit different than what we are used to but it seems to be for the best. Typesafety is at the center of the lib and it is so good. Data fetching is also handled in a very efficient way and it is a perfect fit with react-query. I am so excited to see where it will go and I am sure it will be a big success.

If I have an SPA project to start, I would definitely give it a try and make me even more dependent to TanStack libraries !

Comments

No comments yet.

Add a comment

You must be logged in to comment. Click here to sign in.