useState is the right tool for state one component owns: a text field, an expanded card. Trouble starts when state is shared. Lifting it up and passing props works for a few levels, and a context (Responsive Hooks) removes the prop passing but re-renders every consumer on every change, which is what happened to BookNest's cart. The fix is to notice that "state" means different things:
| Kind of state | BookNest example | Tool |
|---|---|---|
| Local UI state | Search text, a toggle | useState, useReducer |
| Rarely changing, app-wide | Theme, signed-in user | Context |
| Client state, shared and changing | Cart, filters | Zustand 58,763 , Redux Toolkit 3,824 , Jotai 21,282 |
| Server state | Catalog, book details, favorites | TanStack Query 79,069 , RTK Query, SWR 639 |
| Persisted state | Cart across restarts, cached catalog | Persist middleware, AsyncStorage 5,070 |
Server state is the one people most often put in the wrong place. It isn't yours: the server owns it, your copy goes out of date, and several screens may ask for it at once. Copying fetch results into a global store means writing loading flags, error handling, deduplication, caching, refetching and retries yourself. TanStack Query does all of that, and what remains for a client store is small: in BookNest, only the cart.