· TanStack Router · 5 min read
TanStack Router: Query Parameters & Validators
A state manager in the URL, typesafe
Welcome to the third article of a series where we will explore TanStack Router, the new typesafe routing library (and state manager, in some cases) for React.
In the first article we saw how to set up a new project with TanStack Router and how to create a couple of routes.
In the second article instead, the focus was on path parameters and loaders.
In this article, we’ll see how you can properly parse and handle query parameters (aka search parameters) and how they can be effectively used as a state manager. Besides, imagine being able to share your state by just passing the URL, how convenient!
Discover all 21 tutorials I've created on Router and other TanStack libraries for web developers
Browse TutorialsDefine & Validate your Query Parameters
Here’s a demo of what you’re going to learn in this article. For written instructions, keep reading below.
Imagine having a search page or a table with a lot of inputs for filtering the data, or maybe a dashboard with many configurable widgets. You can build your own customized view by setting those filters but as soon as you refresh the page, they’re gone.
Ok, you can save them on localStorage or somewhere in the browser but… what if you want to send your exact same configuration to someone else? A friend? A colleague? Having your settings directly as query parameters in the URL is probably the easiest way, but handling them manually is far from being an easy task.
TanStack Router is designed with this scenario in mind, giving you great control over query params (aka search params). Let’s see how!
Note: I’m assuming you already have the library set up, if not you can check my setup video.
Create your route
Let’s begin with a /search.tsx
empty page and add the link to it in your __root.tsx
.
<Link to="/search" activeProps={activeProps}>
Search
</Link>
Define the type
Now if we want typescript to help us enforcing and validating the correct types, we need to create them.
type ItemFilters = {
query: string;
hasDiscount: boolean;
categories: Category[];
};
type Category = 'electronics' | 'clothing' | 'books' | 'toys';
Write the validating function
With that defined, we should tell TanStack Router that our /search
route has those params. We can do that by implementing and typing the validateSearch
function.
Your search.tsx
file at this point should look like this:
export const Route = createFileRoute("/search")({
component: Search
validateSearch: (search: Record<string, unknown>): ItemFilters => {
return {
query: search.query as string,
hasDiscount: search.hasDiscount === 'true',
categories: search.categories as Category[],
};
},
});
function Search() {
return (<div>Hello /search!</div>);
}
Read the params
Ok cool, now you want to read them I suppose. If you followed the previous chapters, you probably already know that you can use the Route
object in your component and get some hooks from there. useSearchParams
is the answer.
Update your Search component to look like this:
function Search() {
const { query, hasDiscount, categories } = Route.useSearch();
return <pre>{JSON.stringify({ query, hasDiscount, categories }, null, 2)}</pre>;
}
Navigate with the params
Now if you noticed, there should be an error on ___root.tsx
exactly at the Link we created a few steps ago. This is a good sign! TanStack Router is informing us that our path has some required parameters you must provide. Let’s do it.
<Link
to="/search"
activeProps={activeProps}
search={{
query: 'hello',
hasDiscount: true,
categories: ['electronics', 'clothing'],
}}
>
Search
</Link>
The error is gone! It’s time to go on your browser and click the link. You can now see a page with your params rendered as JSON.
## Validation with a validating library
That kind of validator function we defined works, but we can do something better by using an existing validating library such as zod or valibot. Let’s see the latter in an example.
npm i valibot
Define your schema
You can now replace your type with the valibot schema.
import * as v from 'valibot';
const Category = v.union([v.literal('electronics'), v.literal('clothing'), v.literal('books'), v.literal('toys')]);
const ItemFilters = v.object({
query: v.optional(v.string()),
hasDiscount: v.optional(v.boolean()),
categories: v.optional(v.array(Category)),
});
type ItemFilters = v.Output<typeof ItemFilters>;
type Category = v.Output<typeof Category>;
With that, your validation function will be as easy as this:
validateSearch: (search) => v.parse(ItemFilters, search),
Query params as application state
The last cool feature we’re gonna see today is how you can in fact use the query params as your application state. Until now we are only able to read them, but you can indeed write them in your app and see them updated real time in the URL.
The trick is to use the navigate
function to set our params to the new state, in sync with our input.
In the video I build this step by step and the final result is as follows:
function Search() {
const { query, hasDiscount, categories } = Route.useSearch();
const navigate = useNavigate({ from: Route.fullPath });
const updateFilters = (name: keyof ItemFilters, value: unknown) => {
navigate({ search: (prev) => ({ ...prev, [name]: value }) });
};
return (
<div>
<h2>Search</h2>
You searched for: <input
value={query}
onChange={(e) => {
updateFilters('query', e.target.value);
}}
/>
<input type="checkbox" checked={hasDiscount} onChange={(e) => updateFilters('hasDiscount', e.target.checked)} />
<select
multiple
onChange={(e) => {
updateFilters(
'categories',
Array.from(e.target.selectedOptions, (option) => option.value)
);
}}
value={categories}
>
{['electronics', 'clothing', 'books', 'toys'].map((category) => (
<option key={category} value={category}>
{category}
</option>
))}
</select>
<pre>{JSON.stringify({ query, hasDiscount, categories }, null, 2)}</pre>
</div>
);
}
We can now edit all query params, see the changes in the URL and easily share the state with anyone else by just… sending them the link!
Conclusion
We now have a state manager perfectly in sync with our URL, easy to share and validated with Typescript to ensure our params are always correct, with no unnecessary or missing values.
Wow, this library is so powerful! I hope you’re enjoying this series on TanStack!
The next chapter will be about authenticated routes, basically some pages that can only be accessed if a condition is met, for example the user is logged in. Stay tuned
Watch the full playlist on YouTube: TanStack Router
You can find the full code on this repository on the 03-query-parameters. Leave a ⭐️ if you found the demo code useful!
Hello! My name is Leonardo and as you might have noticed, I like to talk about Web Development and Open Source!
I use GitHub every day and my favourite editor is Visual Studio Code... this might influence a little bit my conent! :D
If you like what I do, you should have a look at my YouTube Channel!
Let's get in touch, feel free to send me a DM on Twitter!