Redux vs Alternatives — Choosing the Right State Tool

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
Note: RTK Query and TanStack Query (React Query) solve similar problems — managing server state, caching, background refetching, and cache invalidation. RTK Query’s advantage is tight Redux integration: your server data lives in the Redux store alongside your UI state, accessible via Redux DevTools. TanStack Query’s advantages are framework agnosticism (works without Redux), a slightly more ergonomic API, and larger ecosystem. If you are already using Redux for UI state, RTK Query is the natural choice. If you are not using Redux, TanStack Query is excellent.
Tip: The best architecture for most React applications is a layered approach: local 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.
Warning: Avoid the common anti-pattern of storing server data in Redux store alongside your own UI state. When you fetch posts into 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.
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.

🧠 Test Yourself

Your blog application has: auth state (user + tokens), a posts list from the API, and a modal open/close toggle. How should each be managed?