GitHub

State Taxonomy

Three categories of state — local, shared, and server — and exactly which tool handles each one.

01

Principle

Not all state is the same. Before reaching for any state management library, ask one question: where does this data come from? Local state lives inside one component. Shared state is UI state needed by multiple components. Server state comes from an API and has its own lifecycle — loading, error, stale, and needs refreshing. Each category has a different tool, and mixing them up causes bugs that are hard to trace.

lightbulb

When you find yourself putting API data into Zustand, stop. Server state belongs in React Query. When you find yourself using React Query for a toggle or a modal, stop. UI state belongs in useState or Zustand.

02

Rules

  • check_circle
    Local state: useStateIf only one component needs it, keep it local. A form input value, a toggle, a hover state — these are all local state.
  • check_circle
    Shared state: ZustandIf multiple components need the same UI state — sidebar open/closed, active theme, search dialog open — use Zustand. This is not server data.
  • check_circle
    Server state: React QueryIf it comes from an API, it is server state. React Query handles caching, background refetching, loading states, and error states automatically.
  • check_circle
    Never put server state in ZustandStoring API data in Zustand means you manage caching, staleness, and loading manually. React Query already does this — use the right tool.
03

Pattern

The three categories — decision guide
// ─── LOCAL STATE ──────────────────────────────────────────────
// One component needs it. No sharing needed.
const [isOpen, setIsOpen] = useState(false);
const [inputValue, setInputValue] = useState('');
const [hovering, setHovering] = useState(false);

// ─── SHARED STATE (Zustand) ───────────────────────────────────
// Multiple components need the same UI state.
// This is NOT data from an API.
const { sidebarOpen, toggleSidebar } = useAppStore();
const { theme, setTheme } = useAppStore();
const { open: searchOpen } = useSearchStore();

// ─── SERVER STATE (React Query) ───────────────────────────────
// Comes from an API. Has loading, error, and cache lifecycle.
const { data: users, isLoading, error } = useUsers();
const { data: user } = useUser(id);

// ❌ WRONG — API data in Zustand
const useUserStore = create((set) => ({
  users: [],
  fetchUsers: async () => {
    const data = await usersService.getAll(); // ← belongs in React Query
    set({ users: data });
  },
}));

// ✅ RIGHT — API data in React Query, UI state in Zustand
const { data: users } = useUsers();             // React Query
const { activeFilter } = useFilterStore();      // Zustand
04

Implementation

info

Version Compatibility

Requires React 19+ and the latest stable versions of all dependencies shown.

In Next.js App Router, Server Components fetch server state directly — no React Query or Zustand needed. React Query is for Client Components that fetch after mount.

Taxonomy in Next.js App Router
// ─── SERVER STATE in Server Components ───────────────────────
// fetch() directly — no React Query, no Zustand
export default async function UsersPage() {
  const users = await usersService.getAll(); // Direct async call
  return <UserList users={users} />;
}

// ─── SERVER STATE in Client Components ───────────────────────
// Need to fetch after interaction? Use React Query
'use client';
export function UserSearch() {
  const [query, setQuery] = useState('');
  const { data: users } = useQuery({
    queryKey: ['users', 'search', query],
    queryFn: () => usersService.search(query),
    enabled: query.length > 2,
  });
  // ...
}

// ─── SHARED STATE ─────────────────────────────────────────────
// UI state only — no API data
'use client';
export function Sidebar() {
  const { sidebarOpen, toggleSidebar } = useAppStore();
  return (
    <aside className={sidebarOpen ? 'w-64' : 'w-0'}>
      <button onClick={toggleSidebar}>Toggle</button>
    </aside>
  );
}
menu_book
React Patterns

Helping developers build robust React applications since 2025.

© 2025 React Patterns Cookbook. Built with ❤️ for the community.
react-principles