Server State vs Client State
A lot of “state management” pain comes from storing two very different kinds of data in the same place. Separating them is one of the highest-leverage architecture decisions in a frontend app. Tools such as TanStack Query exist because of the server state vs client state split.
1. Definitions
| Client state | Server state | |
|---|---|---|
| Examples | Modal open, selected tab, form draft, theme | User profile, product list, comments |
| Source of truth | The browser | A remote server |
| Ownership | Synchronous, fully owned by the app | Asynchronous, shared with other users, can go stale |
2. Why server state is hard
Server data is a cache of something you do not own. Treating it like client state means re-implementing:
- Loading and error status per request
- Deduplicating identical requests fired by multiple components
- Caching and deciding when data is stale
- Background refetching (on window focus, reconnect, or interval)
- Invalidating related data after a mutation
- Pagination and infinite scroll
- Optimistic updates with rollback on failure
3. Server-state libraries
Tools like TanStack Query, SWR, RTK Query, and Apollo Client handle these concerns with a query key based cache.
const { data, isLoading, error } = useQuery({
queryKey: ['todos', { status }],
queryFn: () => fetchTodos(status),
staleTime: 30_000,
});
const mutation = useMutation({
mutationFn: addTodo,
onSuccess: () =>... premium content.