Skip to main content

Command Palette

Search for a command to run...

Managing State the Right Way: Zustand for Client State & TanStack Query for Server State

Published
β€’3 min read
Managing State the Right Way: Zustand for Client State & TanStack Query for Server State
A

I’m Arjun Saxena, a passionate software developer specializing in web engineering. I believe in writing code that creates real solutions to real problems. I love building efficient, user-friendly applications and constantly push myself to learn new technologies. Beyond coding, I enjoy sharing knowledge and growing together with others in the tech community.

When building modern React apps, state management becomes very important.
But here’s the mistake many beginners make πŸ‘‡
πŸ‘‰ They try to manage everything with one tool.

In reality, there are two different types of state:

  • Client-side state

  • Server-side state

In this blog, you’ll learn:

  • What client state vs server state really means

  • Why Zustand is perfect for global client state

  • Why TanStack Query is best for server state

  • How caching & optimistic UI work (in easy words)

  • When to use what βœ…

First, Understand State with a Simple Analogy 🧠

Think of a food delivery app πŸ”

Client State (Local App State)

Things that exist only inside your app:

  • Dark / light mode

  • Sidebar open or closed

  • Logged-in user info

  • Selected filters

πŸ‘‰ This is client-side state

Server State (Remote Data)

Things that come from a server / API:

  • Products list

  • User profile from backend

  • Orders data

  • Notifications

πŸ‘‰ This is server-side state

Client State vs Server State (Very Clear Comparison)

Client StateServer State
Lives in browserLives on server
UI-relatedData-related
Controlled by user actionsControlled by API
Zustand is great hereTanStack Query is great here

Using Zustand for Global Client-Side State 🐻

Zustand is:

  • Simple

  • Lightweight

  • No boilerplate

  • Perfect for UI & global state

Creating a Zustand Store (Easy Example)

Step 1: Install

npm install zustand

Step 2: Create a Store

import { create } from "zustand";

const useStore = create((set) => ({
  count: 0,
  increase: () => set((state) => ({ count: state.count + 1 })),
  decrease: () => set((state) => ({ count: state.count - 1 }))
}));

Step 3: Use It in Component

function Counter() {
  const { count, increase, decrease } = useStore();

  return (
    <>
      <h2>Count: {count}</h2>
      <button onClick={increase}>+</button>
      <button onClick={decrease}>-</button>
    </>
  );
}

πŸŽ‰ That’s it!
No reducers, no actions, no providers.

When Should You Use Zustand?

Use Zustand for:

  • Theme (dark / light)

  • Auth state (logged in or not)

  • Cart state

  • UI toggles

❌ Do NOT use Zustand for fetching server data

Using TanStack Query for Server-Side State 🌐

TanStack Query (React Query):

  • Fetches data

  • Caches data

  • Syncs data with server

  • Handles loading & error states automatically

Installing TanStack Query

npm install @tanstack/react-query

Basic Data Fetching Example

Setup Query Client

import { QueryClient, QueryClientProvider } from "@tanstack/react-query";

const queryClient = new QueryClient();

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <Users />
    </QueryClientProvider>
  );
}

Fetch Data with useQuery

import { useQuery } from "@tanstack/react-query";

function Users() {
  const { data, isLoading, error } = useQuery({
    queryKey: ["users"],
    queryFn: () =>
      fetch("https://jsonplaceholder.typicode.com/users")
        .then(res => res.json())
  });

  if (isLoading) return <p>Loading...</p>;
  if (error) return <p>Error!</p>;

  return data.map(user => <p key={user.id}>{user.name}</p>);
}

βœ”οΈ Loading handled
βœ”οΈ Error handled
βœ”οΈ Data cached

Caching: Why TanStack Query Is Powerful ⚑

Once data is fetched:

  • It is stored in cache

  • No extra API calls

  • Fast UI

πŸ‘‰ If user revisits the page, data loads instantly.

Optimistic UI (Magic Explained Simply ✨)

Optimistic UI means:

β€œUpdate the UI before server confirms”

Real-Life Example πŸ›’

You click Like πŸ‘
UI updates instantly, even before API response.

Simple Optimistic Update Example

useMutation({
  mutationFn: addTodo,
  onMutate: async (newTodo) => {
    queryClient.setQueryData(["todos"], old => [...old, newTodo]);
  }
});

πŸ‘‰ User feels app is fast & smooth

Zustand + TanStack Query Together 🀝

Best practice:

  • Zustand β†’ UI & global client state

  • TanStack Query β†’ API & server state

They solve different problems.

Architecture Diagram Idea πŸ’‘

User Action
   ↓
UI Component
   ↓
Zustand (Client State)
   ↓
TanStack Query (Server State)
   ↓
API / Backend