Hint
Server state lives on the backend (stale, async, shared); client state is local UI state — they need different tools
Two fundamentally different problems often lumped together:
Client state — UI-only data: is the modal open? which tab is selected? filter selections. Lives in the browser, synchronous, you own it completely.
Server state — data fetched from an API: user profile, products, orders. Lives on the server, asynchronous, can be stale, multiple components may need the same data, can change externally.
// ❌ Treating server state like client state
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
useEffect(() => {
setLoading(true);
fetch('/api/users')
.then(r => r.json())
.then(setUsers)
.catch(setError)
.finally(() => setLoading(false));
}, []); // No caching, no refetch, no deduplication
// ✅ React Query handles all of it
const { data: users, isLoading, error } = useQuery({
queryKey: ['users'],
queryFn: () => fetch('/api/users').then(r => r.json()),
staleTime: 5 * 60 * 1000, // cache for 5 minutes
});