I was recently building a dashboard that needed to handle real-time data updates, user preferences, and complex UI controls. The sheer amount of state—some from APIs, some from user interactions—felt overwhelming. I found myself asking: why am I storing a server-fetched user list in the same place as a sidebar toggle? This friction led me to a powerful combination: using Zustand for what happens on the screen and React Query for what comes from the server.
Think about your current project. Are you mixing API data with UI state, causing unnecessary complexity and re-renders? You’re not alone. Many developers start by putting everything into a single state management solution, which often leads to messy code.
Let’s look at how these tools work individually first. React Query is not a traditional state manager. It’s a server-state library. Its job is to fetch, cache, synchronize, and update asynchronous data. You give it a query key and a fetching function, and it handles the loading, error, and success states brilliantly.
import { useQuery } from '@tanstack/react-query';
function UserProfile({ userId }) {
const { data, isLoading, error } = useQuery({
queryKey: ['user', userId],
queryFn: () => fetchUserById(userId),
});
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return <div>Hello, {data.name}</div>;
}
Notice how it manages the request lifecycle automatically. The data is cached under the key ['user', userId]. If another component uses the same key, it shares the cache. This eliminates duplicate network requests.
So, where does Zustand fit in? It manages client state—the state that never needs to hit a server. This includes whether a modal is open, a dark mode theme is active, or a form is in a draft state. It’s incredibly simple.
import { create } from 'zustand';
const useThemeStore = create((set) => ({
isDarkMode: false,
toggleTheme: () => set((state) => ({ isDarkMode: !state.isDarkMode })),
}));
// Using it in a component
function ThemeToggle() {
const { isDarkMode, toggleTheme } = useThemeStore();
return <button onClick={toggleTheme}>Mode: {isDarkMode ? 'Dark' : 'Light'}</button>;
}
The store is a hook. You create it once, and any component can use pieces of its state. It requires far less code than other options and doesn’t force your components to re-render unless the specific piece of state they use changes.
Now, here’s the crucial part: how do they work together? The secret is a clear separation of concerns. React Query owns anything asynchronous or server-related. Zustand owns everything that is synchronous and local to the UI.
Consider a user settings page. You might fetch the user’s profile from an API (React Query’s job) and manage the state of an “edit mode” toggle (Zustand’s job). They live side by side, cleanly separated.
import { useQuery } from '@tanstack/react-query';
import useUiStore from './stores/uiStore';
function SettingsPage() {
// Server State from React Query
const { data: user } = useQuery({
queryKey: ['user-settings'],
queryFn: fetchUserSettings,
});
// Client State from Zustand
const { isEditMode, enableEditMode } = useUiStore();
return (
<div>
<h1>Settings for {user?.name}</h1>
<button onClick={enableEditMode}>Edit Preferences</button>
{isEditMode && <SettingsForm />}
</div>
);
}
This separation makes your application easier to understand. When you look at a piece of state, you immediately know its source and its scope. Debugging becomes more straightforward. Performance improves because UI interactions don’t trigger unnecessary refetches, and server data updates don’t cause unrelated UI components to re-render.
What happens when you need to combine them? Sometimes, you might derive a piece of local state from server data. The pattern is simple: keep the server data in React Query and use it to populate a Zustand store only if needed for local mutations before saving.
For instance, imagine a dashboard with configurable widgets. You would fetch the widget layout from the server with React Query. When a user starts dragging widgets around, you’d use a Zustand store to manage the temporary, unsaved arrangement. Once the user hits “save,” you’d send the new arrangement from the Zustand store back to the server, which would invalidate the React Query cache and trigger a fresh fetch.
This approach scales beautifully. As your application grows, this clear boundary prevents the state management from becoming a tangled mess. New developers on the team can quickly learn where to put new state. The architecture remains lightweight because you’re using each tool for its specific strength.
Are you ready to simplify your state management? Start by auditing your current global store. Move all your fetch calls and useEffect hooks for data fetching into React Query useQuery hooks. Then, identify the state that’s left—theme flags, form drafts, sidebar visibility. That’s your new Zustand store.
The result is a faster, more maintainable application. You’ll write less boilerplate code, avoid common performance pitfalls, and create a foundation that’s easy to build upon for years to come. Give this pattern a try in your next feature.
I’d love to hear how this approach works for you. What challenges have you faced with state management in the past? If you found this breakdown helpful, please share it with a teammate or leave a comment below with your thoughts.
As a best-selling author, I invite you to explore my books on Amazon. Don’t forget to follow me on Medium and show your support. Thank you! Your support means the world!
101 Books
101 Books is an AI-driven publishing company co-founded by author Aarav Joshi. By leveraging advanced AI technology, we keep our publishing costs incredibly low—some books are priced as low as $4—making quality knowledge accessible to everyone.
Check out our book Golang Clean Code available on Amazon.
Stay tuned for updates and exciting news. When shopping for books, search for Aarav Joshi to find more of our titles. Use the provided link to enjoy special discounts!
📘 Checkout my latest ebook for free on my channel!
Be sure to like, share, comment, and subscribe to the channel!
Our Creations
Be sure to check out our creations:
Investor Central | Investor Central Spanish | Investor Central German | Smart Living | Epochs & Echoes | Puzzling Mysteries | Hindutva | Elite Dev | JS Schools
We are on Medium
Tech Koala Insights | Epochs & Echoes World | Investor Central Medium | Puzzling Mysteries Medium | Science & Epochs Medium | Modern Hindutva