State Management
“Which state library should I use?” starts with a better question: what kind of state is it? Classify it, and the right tool is usually obvious — often no library at all.
On this page · 6 min · Intermediate
On this page
- Learning objectives
- Explanation
- Syntax
- Code example
- Step by step
- Real-world use
- Common mistakes
- Best practices
- Remember this
- Practice exercise
- Mini quiz
- Related topics
- My notes
- Reading time
- 6 min
- Difficulty
- Intermediate
- Updated
- Oct 28, 2026
Related tutorials
Learning objectives#
- Classify state as local, shared, server, URL or form state
- Choose the simplest tool for each kind
- Compare Context, Redux Toolkit and Zustand
- Know why server data deserves its own tool
Explanation#
Local UI state (is this dropdown open?) → useState in the component.
Shared client state (cart, theme, signed-in user) → lift state up or Context. When it grows large or changes often, use a store library: Redux Toolkit (structured, great DevTools, common in large teams) or Zustand (tiny, simple hooks).
Server state (products, orders) is a cache of data owned by the backend. TanStack Query or SWR handle fetching, caching, refetching and invalidation — do not copy it into Redux by hand.
URL state (page number, filters, selected tab) → search params, so it survives refresh and can be shared.
Form state → controlled inputs or a form library like React Hook Form.
Rule of thumb: start with useState and props; add tools only when a real pain appears.
Syntax#
Kind of state Example Tool
local UI modal open? useState
shared client cart, theme, user Context / Zustand / Redux Toolkit
server cache products, orders TanStack Query / SWR
URL ?page=2&q=pen useSearchParams
form sign-up fields controlled inputs / React Hook Form
Code example: A tiny global store with useSyncExternalStore#
import { useSyncExternalStore } from 'react';
// A minimal store — the idea behind Zustand and Redux, in 20 lines
function createStore(initialState) {
let state = initialState;
const listeners = new Set();
return {
getState: () => state,
setState: update => {
state = { ...state, ...(typeof update === 'function' ? update(state) : update) };
listeners.forEach(l => l());
},
subscribe: listener => {
listeners.add(listener);
return () => listeners.delete(listener);
},
};
}
const cartStore = createStore({ items: [] });
function useCartStore(selector) {
return useSyncExternalStore(cartStore.subscribe, () => selector(cartStore.getState()), () => selector(cartStore.getState()));
}
const addItem = name => cartStore.setState(s => ({ items: [...s.items, name] }));
function CartCount() {
const count = useCartStore(s => s.items.length); // re-renders only when the count changes
return <p>Cart: {count}</p>;
}
function AddButtons() {
return ['Pen', 'Bag'].map(name => <button key={name} onClick={() => addItem(name)}>Add {name}</button>);
}
export default function App() {
return (
<>
<CartCount />
<AddButtons /> {/* no provider, no props */}
</>
);
}
“Cart: 0” and two buttons. Each click increases the count. CartCount and AddButtons share state through the store without props or a provider — this is the model Zustand and Redux build on (with Redux adding actions, reducers and DevTools).
This example is illustrative. Copy or download it and run it in your own environment; the expected output is shown above.
Step-by-step explanation#
- createStore keeps state outside React and notifies subscribers on change.
- useSyncExternalStore is React’s official hook for reading external stores safely.
- The selector lets CartCount subscribe to just the count.
- addItem can be called from anywhere — even outside components.
Real-world use case#
A large SaaS dashboard uses TanStack Query for all API data, Zustand for UI preferences and the sidebar state, search params for table filters, and plain useState inside each widget.
Common mistakes#
You then reinvent caching, loading flags and refetching. Use a server-state library.
A form’s input values in a global store re-render half the app on each keystroke. Keep it local.
Start simple. Many apps never need more than useState, context and a data-fetching library.
Best practices#
- Classify state before choosing a tool
- Keep state as local as possible
- Use a server-cache library for API data
- Put shareable UI state in the URL
Remember this#
Local → useState. Shared → Context/Zustand/Redux. Server → TanStack Query.
URL state = filters and pages that should survive refresh.
Start simple; add a library when it hurts.
Practice exercise#
Your task: Classify each: (1) whether a tooltip is visible, (2) the logged-in user, (3) the list of products from the API, (4) the current page number of a search, (5) the text in a comment box.
Need a hint?
Show solution
1. Tooltip visible → local UI state (useState)
2. Logged-in user → shared client state (Context or a store)
3. Products from the API → server state (TanStack Query / SWR, or fetch + state)
4. Search page number → URL state (useSearchParams)
5. Comment box text → form/local state (controlled input)
Mini quiz#
Which tool is designed for caching and refetching API data?
Want more? Practise all React MCQs · React interview questions
My notes#
Saved in this browser.