Zustand vs Tanstack Query: Maybe Both?
People often ask if they should use Zustand or Tanstack Query. The answer could be maybe both?
August 30, 2025
Often when scrolling through the internet, I’ve noticed that people ask if they should use Zustand or Tanstack Query as a state management solution for their React applications.
While Tanstack Query is a great tool that handles server state, fetching data from an API and caching it, Zustand is a great tool for managing state “within” your application.
Tanstack Query is an async manager with built in cache invalidation to be used in API calls with a really good developer experience using its “Devtools” that lets you monitor your queries and their states, it’s just great having access to loading and error states out of the box with a few lines of code.
Zustand is just a small state management library that’s easy to understand and use. I personally often find it a nice middle ground setting between React Context and Redux.
Let’s put a real case scenario where both of them can be used.
Imagine you’re building an application where you will need to fetch a list of products and you want to select a product to purchase, you’ll need to fetch data from an API, cache it and then use it in your application.
Looking at the example below, you can see how easy it is to fetch data from an API and use it in your application, throw an error if the request fails and show a cute spinner while the request is loading.
import { useQuery } from "@tanstack/react-query";
const { data, isLoading, error } = useQuery({
queryKey: ["products"],
queryFn: async (): Promise<Array<Product>> => {
const response = await fetch(
"https://jsonplaceholder.typicode.com/products",
);
return await response.json();
},
});
Remember this from the docs: “For TanStack Query to determine a query has errored, the query function must throw or return a rejected Promise. Any error that is thrown in the query function will be persisted on the error state of the query”
And since I mentioned queries for data fetching, I must give a mention to the big lovely mutations to create and update data on the server.
Now switching to Zustand, out of the fetched products, where we can use those cached queries in the rest of our application, when a user selects a product to purchase, we can use Zustand to store the selected product in the internal application state and move on to the checkout process. I’ll simply create a product type and a store to handle the selected product.
import { create } from "zustand";
type Product = {
id: number;
name: string;
price: number;
};
type ProductStore = {
product: Product | null;
setProduct: (product: Product | null) => void;
clearProduct: () => void;
};
export const useProductStore = create<ProductStore>((set) => ({
product: null,
setProduct: (p) => set({ product: p }),
clearProduct: () => set({ product: null }),
}));
I think it’s a pretty solution that solves real problems and make server states more manageable, while keeping the simplicity of client states with Zustand with no unnecessary boilerplates.
While writing this post I am still exploring the new Tanstack Store which is in its alpha stage, I could give it a shot and see how it works, if it’s any good im happy to share my findings in another post.