Concept
This domain covered vanilla Redux (createStore, hand-written reducers and action creators, applyMiddleware) in redux-core, then Redux Toolkit (configureStore, createSlice, createAsyncThunk, createEntityAdapter, createApi) in redux-toolkit. This topic makes the relationship between them explicit: RTK is not a competing library or a different mental model, it's the same underlying Redux, with generated code and safety nets layered on top.
Side by side: identical underlying behavior, different ceremony
// VANILLA, from Actions & Reducers
function counterReducer(state = { value: 0 }, action) {
switch (action.type) {
case "counter/incremented":
return { value: state.value + 1 };
default:
return state;
}
}
function incremented() {
return { type: "counter/incremented" };
}
const store = createStore(counterReducer);// RTK, from createSlice
const counterSlice = createSlice({
name: "counter",
initialState: { value: 0 },
reducers: {
incremented(state) { state.value += 1; },
},
});
const store = configureStore({ reducer: counterSlice.reducer });Confirmed throughout the redux-toolkit topics: dispatching incremented() against either store produces the exact same resulting state shape, the exact same action type string ("counter/incremented"), and the store itself exposes the identical getState/dispatch/subscribe/replaceReducer surface confirmed in Store. RTK's version is shorter and safer to write (Immer prevents accidental mutation, confirmed in createSlice), but it is not a different system underneath.
store.dispatch({ type: 'counter/incremented' });
dispatch() sends the action to the store's single root reducer, this is the ONLY way state changes in Redux; nothing else can mutate the store.
What RTK adds, concretely (not replaces)
| Vanilla Redux | Redux Toolkit equivalent | What's confirmed added |
|---|---|---|
createStore + applyMiddleware + composeWithDevTools | configureStore | DevTools + thunk wired automatically; dev-only mutation-detection middleware (confirmed to throw on real mutation) and serializability-check middleware (confirmed to warn, not throw) |
Hand-written switch reducer + manual immutable spreading | createSlice | Immer-powered "mutating" syntax, confirmed to produce genuinely immutable output with deep structural sharing |
Every single row on the left is possible in vanilla Redux, nothing about RTK's additions requires abandoning Redux's core model. What changes is how much of it you have to write and re-verify by hand, and how many of the Essential-tier rules from Best Practices are mechanically enforced versus relying on developer discipline.
The one genuinely new capability: RTK Query has no vanilla-Redux equivalent
Everything else in the table above is "the same thing, generated", but RTK Query's confirmed cache-key deduplication and tag-based invalidation isn't something vanilla Redux does at all without substantial hand-built infrastructure (which would, in practice, end up re-implementing a worse version of RTK Query itself). This is the strongest, least-arguable reason to prefer RTK for any new data-fetching-heavy Redux code specifically.
Try It
Predict the outcome before checking the solution.
// A team migrates an existing vanilla-Redux reducer to createSlice,
// keeping the exact same action type strings and state shape.
// Does any EXISTING code that dispatches plain action objects like
// { type: "counter/incremented" } (written before the migration,
// NOT using the new slice's generated action creators) still work?After migrating the reducer to createSlice but leaving old call sites that dispatch hand-written plain action objects unchanged, do those old dispatches still function correctly?
Solution
Yes, as long as the action type strings match exactly, old plain-object dispatches continue to work correctly against a createSlice-generated reducer.
This follows directly from the confirmed fact that RTK produces the same kind of reducer function and the same kind of action-type-based dispatch mechanism as vanilla Redux, a createSlice reducer's generated case matching is still just comparing action.type strings, exactly like the hand-written switch statement it replaced. Since createSlice's auto-generated action types follow the predictable "sliceName/reducerKey" pattern, an old plain object { type: "counter/incremented" } dispatched by hand matches the migrated reducer's generated case exactly, and the migration doesn't require touching every dispatch call site simultaneously, a genuinely useful property for incremental, low-risk migrations from vanilla Redux to RTK.
Implement It Yourself
Sketch a minimal comparison harness that dispatches the same action against both a vanilla and an RTK-built store, to see the equivalence directly:
// Vanilla
function vanillaReducer(state = { value: 0 }, action) {
if (action.type === "counter/incremented") return { value: state.value + 1 };
return state;
}
const vanillaStore = createStore(vanillaReducer);
// RTK
const slice = createSlice({
name: "counter",
initialState: { value: 0 },
reducers: { incremented(state) { state.value += 1; } },
});
const rtkStore = configureStore({ reducer: slice.reducer });
Both stores, dispatched with the literal same plain action object, land on the identical resulting state, direct, runnable proof that RTK's reducers are genuinely the same kind of thing as vanilla reducers, just generated with additional safety and less code.
Under the Hood
This topic is the explicit synthesis of the entire domain built so far: redux-core established the underlying mechanics (store, dispatch, reducers, middleware, selectors) that both vanilla Redux and RTK share identically; redux-toolkit showed RTK automating and safety-netting those same mechanics. RTK Query is the one piece that goes meaningfully beyond "automate the same thing" into genuinely new territory (a purpose-built caching layer), which is why it's called out specifically here as the strongest standalone argument for RTK.
Common Mistakes
1. Treating "should we use Redux Toolkit" as a separate architectural decision from "should we use Redux"
// Misconception: "let's evaluate RTK vs vanilla Redux vs Zustand vs MobX as four peer options"RTK isn't a peer alternative to Redux, it's Redux, specifically Redux's own officially-recommended way to write Redux. The real four-way comparison (Redux vs Zustand vs MobX vs Jotai, covered in Redux vs Zustand vs MobX vs Jotai) should assume RTK as the default way of using Redux, not treat "vanilla Redux" as the thing being compared against other libraries.
2. Assuming a full rewrite is required to adopt RTK in an existing vanilla-Redux codebase
// Belief: "we can't use configureStore unless every reducer is rewritten as createSlice first"Confirmed false, per the Try It exercise: configureStore accepts plain, hand-written reducer functions directly, a team can adopt configureStore immediately for its automatic DevTools/thunk/safety-middleware benefits, then migrate individual reducers to createSlice incrementally, slice by slice, with old dispatch call sites continuing to work throughout.
3. Avoiding RTK due to "too much magic" without examining what it actually automates
// Vague objection: "createSlice does too much automatically, I want to see the real reducer"Every mechanic RTK automates (Immer-based immutability, DevTools wiring, mutation detection) has been shown throughout this domain to be a concrete, inspectable, verifiable behavior, not opaque magic. createSlice's generated reducer is a real, ordinary reducer function; it's just generated rather than hand-typed.
Best Practices
- Default to Redux Toolkit for all new Redux code, this is Redux's own official recommendation, not a third-party opinion, and every mechanic it automates has been confirmed throughout this domain to produce identical, correct underlying behavior with meaningfully less risk of Essential-tier rule violations.
- Migrate existing vanilla-Redux codebases incrementally, starting with
configureStore(a drop-in replacement requiring no reducer changes), then migrating individual reducers tocreateSliceover time. - Treat RTK Query as the strongest standalone reason to adopt RTK for any codebase with meaningful data-fetching needs, given it has no vanilla-Redux equivalent at all.
Performance Tips
- RTK's dev-only safety middlewares (mutation detection, serializability checks) add real overhead in development, confirmed stripped from production builds, there's no production performance cost to choosing RTK over vanilla Redux specifically because of these checks.
- Immer's proxy-based "mutating" syntax has small inherent overhead compared to hand-written direct spreading, for virtually all apps this is negligible against the reduction in bugs and boilerplate; it's not a meaningful factor in the RTK-vs-vanilla decision for typical apps.
