In previous tutorials, we talked about server functions. middlewares, how to fetch data and load it into your components. Let’s start integrating one of the most powerful tools of using anything TanStack related, which is TanStack Query. TanStack Query is the first TanStack library that was created and it is one of the best libraries in the React ecosystem. Personally, I don’t think it makes sense to use TanStack without using TanStack Query.
TanStack Query #
There is already a example route for TanStack Query in the starter template. Go to http://localhost:3000/demo/tanstack-query to see it in action. You can find the code for this route in src/routes/demo/tanstack-query.tsx. You might notice that this route is very similar to the loader example we saw in the previous tutorial. The main differences are that we are using TanStack Query’s useQuery and useMutation hooks and few others. To integrate TanStack Query into a TanStack Start project, it makes it in my opinion such that your components are more well organized. But you might notice most of the logic is not present in this file. The reason for that is when you are going to, for example, fetch a list of todos using the useQuery hook, you are not actually using a loader like we were before. There is no loader in this route. You use the useQuery hook and you fetch the data directly from an API endpoint. An API endpoint in TanStack Start is known as a server route.
const { data, refetch } = useQuery<Todo[]>({
queryKey: ['todos'],
queryFn: () => fetch('/demo/api/tq-todos').then((res) => res.json()),
initialData: [],
})You might be familiar with something like above in Next.js. But where does the /demo/api/tq-todos endpoint come from? Well, to create an API endpoint in TanStack Start, you create a route file in the src/routes which starts with api.. In our case, the file is named src/routes/demo/api.tq-todos.ts.
If you open that file, we have just a simple list of todos. To create a server route, you call the createFileRoute function, you put the endpoint path as the first argument.
export const Route = createFileRoute('/demo/api/tq-todos')({
server: {
handlers: {
GET: () => {
return Response.json(todos)
},
POST: async ({ request }) => {
const name = await request.json()
const todo = {
id: todos.length + 1,
name,
}
todos.push(todo)
return Response.json(todo)
},
},
},
})Then you define the HTTP methods you want to support in the handlers object. In our case, we have a GET handler that returns the list of todos and a POST handler that adds a new todo to the list. This is how you create API endpoints in TanStack Start using TanStack Server Functions.
Then whenever you want to fetch these data, you can fetch it by just fetching directly from the path you defined in the createFileRoute function. You’ll get the JSON response back. With ueQuery, you can just assign this to the queryFn and you’re good to go. Why would you want to do this instead of using loaders? The first reason is that it’s lot more organized. Also there are some actual direct functionality reasons why you might want to use something compred to the other. For example, when you load into your page using a loader, the loader function will run on the server first, then they can block the entire route being rendered until the data is ready. The important thing to understand is since we run that loader in the server and we get that data when the route is loaded, that means that the data is SEO friendly. It’s also great for things that don’t really change that often. We’ve talked about the difference between static and dynamic data in the previous tutorial. I would say think of this as like getServerSideProps in Next.js.
When you are using something like TanStack Query it’s bit different because this is more for dynamic data. This is fetched in the client side after the hydration. This is great for stuff like when you have live feed, you have live dashboards, you have data that changes often, you have data that the user can interact with. This is where TanStack Query is suited the best. The useQuery hook gives you direct availability for caching, background refetching, pagination, infinite scrolling, and a lot more. I personally think that the best thing you can do is you can leverage the benifits of both different methodologies. Same as when you build applications in Next.js, you would be leveraging the methodologies of client components and server components. There are benifits to both of them.
A common pattern is to actually combine both of them in a way that you can actually have useQuery in a route that also has a loader. You just use useQuery for anything that is not fetched when the route is loaded. It’s for something that is fetched later.