Skip to content

State Management

State Management

Local State First

Always start with local state. Only lift state when necessary:

// Start here - local state
function Counter() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}

// Lift only when siblings need the state
function Parent() {
  const [count, setCount] = useState(0);
  return (
    <>
      <Counter count={count} onChange={setCount} />
      <Display count={count} />
    </>
  );
}

TanStack Query for Server State

Use TanStack Query for all server data:

import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';

// Query keys - consistent structure
const userKeys = {
  all: ['users'] as const,
  lists: () => [...userKeys.all, 'list'] as const,
  list: (filters: UserFilters) => [...userKeys.lists(), filters] as const,
  details: () => [...userKeys.all, 'detail'] as const,
  detail: (id: string) => [...userKeys.details(), id] as const,
};

// Fetching data
function useUser(id: string) {
  return useQuery({
    queryKey: userKeys.detail(id),
    queryFn: () => api.users.get(id),
    staleTime: 5 * 60 * 1000, // 5 minutes
  });
}

// Mutating data
function useUpdateUser() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: (data: UserUpdate) => api.users.update(data),
    onSuccess: (user) => {
      queryClient.setQueryData(userKeys.detail(user.id), user);
      queryClient.invalidateQueries({ queryKey: userKeys.lists() });
    },
  });
}

Zustand for Client State

Use Zustand for complex client-side state:

import { create } from 'zustand';
import { persist } from 'zustand/middleware';

interface UIState {
  sidebarOpen: boolean;
  theme: 'light' | 'dark';
  toggleSidebar: () => void;
  setTheme: (theme: 'light' | 'dark') => void;
}

export const useUIStore = create<UIState>()(
  persist(
    (set) => ({
      sidebarOpen: true,
      theme: 'light',
      toggleSidebar: () => set((s) => ({ sidebarOpen: !s.sidebarOpen })),
      setTheme: (theme) => set({ theme }),
    }),
    { name: 'ui-storage' }
  )
);

// Usage
function Sidebar() {
  const { sidebarOpen, toggleSidebar } = useUIStore();
  // ...
}

See Also

  • Form Handling -- Managing form state with react-hook-form and Zod
  • Providers -- React context providers for shared application state