The blog application now has four state management options in its toolkit: React’s built-in useState/useContext, Zustand (Chapter 39), and Redux Toolkit with RTK Query (this chapter). Knowing which to reach for in each situation — and understanding the genuine tradeoffs rather than dogmatic rules — is what separates experienced frontend engineers from those who blindly follow trends. This final lesson compares the tools directly, proposes a pragmatic architecture for the blog application, and helps you make the right choice for your own projects.
State Management Comparison
| Criterion | useState/Context | Zustand | Redux Toolkit | RTK Query |
|---|---|---|---|---|
| Bundle size | 0 kb (built-in) | ~3 kb | ~11 kb | +~5 kb on RTK |
| Boilerplate | Low | Minimal | Moderate | Low (auto-generated) |
| DevTools | React DevTools | Redux DevTools (opt-in) | Redux DevTools (built-in) | RTK Query devtools |
| Re-renders | All consumers | Subscribed slice only | Subscribed slice only | Per-query cache key |
| Caching | None | None | None (need thunks) | Built-in, tag-based |
| Best for | Local/simple global state | Auth, cart, UI state | Complex state, large teams | Server data |
useState for component-scoped state (form inputs, toggle visibility), Zustand or Context for global UI state (auth, theme, toasts), and RTK Query or TanStack Query for all server data (posts, users, comments). This avoids both the over-engineering of putting everything in Redux and the under-engineering of global state chaos. Reach for Redux Toolkit specifically when you need its powerful devtools, structured patterns, or have a large team that benefits from enforced conventions.state.posts.items with a thunk and also use RTK Query for other endpoints, you have two separate caching systems that do not coordinate. Pick one approach for server data: either all thunks (manual cache management) or all RTK Query (automatic cache management). Mixing them in the same application for the same resource type creates inconsistencies and confusion.Recommended State Architecture for the Blog Application
Blog Application State Architecture:
LOCAL STATE (useState):
- Form values (title, body, tags)
- UI toggles (isOpen, isEditing)
- Search query input
- Pagination page number
GLOBAL UI STATE (Zustand — authStore):
- Current user object
- Access token
- Auth loading state
GLOBAL UI STATE (Context — ToastContext):
- Toast notification list
- Toast add/remove functions
SERVER STATE (RTK Query — blogApi):
- Posts list (with pagination/filters)
- Single post detail
- User profiles
- Tags list
- Comments
CROSS-CUTTING UI STATE (Redux — uiSlice):
- Complex multi-step wizard state
- Undo/redo history
- Large team shared UI conventions
The key principle: server state and UI state are different problems.
RTK Query/TanStack Query for server data.
useState/Zustand/Context for UI state.
When Redux Toolkit is Worth the Investment
| Use Redux When | Skip Redux When |
|---|---|
| Large team needs shared conventions | Small team or solo project |
| Complex interdependent state updates | State updates are simple and isolated |
| Full action history needed for debugging | Basic console.log debugging is fine |
| Enterprise application with strict patterns | Startup or prototype |
| Already using Redux ecosystem (RTK, RTK Query) | Starting fresh with no existing Redux |
| Undo/redo functionality required | No complex state history needed |
Complete Store with All Layers
// src/store/index.js — final blog app store
import { configureStore } from "@reduxjs/toolkit";
import authReducer from "./authSlice";
import uiReducer from "./uiSlice";
import { blogApi } from "./apiSlice";
export const store = configureStore({
reducer: {
auth: authReducer,
ui: uiReducer,
[blogApi.reducerPath]: blogApi.reducer,
},
middleware: (getDefault) =>
getDefault().concat(blogApi.middleware),
devTools: import.meta.env.DEV, // DevTools only in development
});
// src/main.jsx
// <Provider store={store}>
// <AuthProvider> ← Zustand auth (Chapter 39)
// <ToastProvider> ← Context toasts (Chapter 39)
// <BrowserRouter>
// <App />
// </BrowserRouter>
// </ToastProvider>
// </AuthProvider>
// </Provider>
Common Mistakes
Mistake 1 — Using Redux for all state (over-engineering)
❌ Wrong — modal open state dispatched through Redux:
dispatch(setModalOpen(true)); // overkill for local component state
✅ Correct — use useState for component-scoped state.
Mistake 2 — Mixing RTK Query and manual thunks for same resource
❌ Wrong — two caches for the same “posts” data:
// Component A: useGetPostsQuery() — RTK Query cache
// Component B: dispatch(fetchPosts()) — manual thunk into state.posts.items
✅ Correct — pick one approach and use it consistently for each resource.