DAY 49: Mastering Redux Toolkit – configureStore, createSlice, Middleware & Selectors | ReactJS


🚀 Introduction
Welcome to Day 49 of my Web Development Journey!
After building a strong foundation in HTML, CSS, and JavaScript, I’ve been diving deep into ReactJS — a powerful library for creating dynamic and interactive user interfaces.
So far, I’ve explored state management in React using useState, Context API, useReducer, and Zustand. Recently, I started learning Redux, one of the most popular and robust libraries for managing state in modern web applications.
Over the past few days, after mastering the core concepts of Redux, I dived into Redux Toolkit (RTK) — the official, modern, and recommended way to use Redux. Here’s what I learned and built:
- Redux Toolkit basics –
configureStore
,createSlice
- Built the
createSlice
method from scratch - Learned about Middleware and Selectors in Redux
📂 You can check out my complete Redux learning progress in my GitHub repository.
I’m documenting this journey publicly to stay consistent and to share my learnings. Whether you’re just starting with React or looking to strengthen your knowledge of state management, I hope this blog adds value to your journey!
👉 Feel free to follow me on Twitter for real-time updates and coding insights.
📅 Here’s What I Covered Over the Last 3 Days
Day 46
- Explored Redux Toolkit and its advantages over core Redux
- Learned how to create a store using
configureStore
- Created my first slice using
createSlice
Day 47
- Built the
createSlice
method of Redux Toolkit from scratch to understand its inner workings - Learned and experimented with Middleware in Redux
Day 48
- Practiced and applied Selectors in Redux for optimized state access
Now, let’s break down each of these concepts with explanations and implementations 👇
1. Redux Toolkit (RTK)
When working with Redux, one of the biggest challenges developers face is its boilerplate code and somewhat complex setup. Writing reducers, defining action types, creating action creators, and then wiring everything together can feel repetitive and overwhelming — especially for beginners.
That’s where Redux Toolkit (RTK) comes in.
It is the official, recommended way to write Redux logic and makes Redux development much simpler, faster, and less error-prone.
Advantages of Redux Toolkit:
- Less Boilerplate: We no longer need to write action types, action creators, and switch cases separately.
- Simplified Store Setup: Provides
configureStore()
which automatically sets up the store with good defaults. - Immutable Updates Made Easy: With ImmerJS under the hood, we can write reducers that “mutate” state directly, and RTK takes care of immutability for us.
- Built-in Best Practices: Middleware like
redux-thunk
is already included, so async logic is easier. - Cleaner Code & Better Readability: Overall, code becomes shorter, more maintainable, and easier to understand.
configureStore() in Redux Toolkit:
In traditional Redux, setting up a store requires manually importing createStore
, applying middleware, and adding enhancers.
With RTK, we use the configureStore()
method, which:
- Creates a Redux store with a single function call.
- Automatically sets up middleware like
redux-thunk
. - Includes Redux DevTools Extension support out of the box.
- Allows easy combination of multiple slices (reducers).
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './counterSlice';
const store = configureStore({
reducer: {
counter: counterReducer,
},
});
export default store;
Here:
- We import
configureStore
from RTK. - Pass an object with our slice reducers inside
reducer
. - Done (no need to manually configure middleware or DevTools).
createSlice() in Redux Toolkit:
The createSlice
method is the real game-changer in RTK.
It allows us to define state, reducers, and action creators — all in one place.
Instead of writing action types and creators separately, createSlice
auto-generates them for us.
import { createSlice } from '@reduxjs/toolkit';
const counterSlice = createSlice({
name: 'counter',
initialState: { value: 0 },
reducers: {
increment: (state) => {
state.value += 1;
},
decrement: (state) => {
state.value -= 1;
},
incrementByAmount: (state, action) => {
state.value += action.payload;
},
},
});
export const { increment, decrement, incrementByAmount } = counterSlice.actions;
export default counterSlice.reducer;
How It Works?
- name: A unique key for this slice of state.
- initialState: Defines the initial data.
- reducers: Contains functions that update the state.
- Auto-generated actions (
increment
,decrement
, etc.) can be dispatched directly. - The reducer is exported and connected to the store.
- The action types generated follow the format:
Example:sliceName/actionName
counter/increment
With this, we eliminate the need for separate actionTypes.js
, actions.js
, and reducers.js
files.
2. Building createSlice Method from Scratch
After learning how Redux Toolkit’s createSlice
works, I decided to implement my own version from scratch.
This helped me understand what happens behind the scenes when we use createSlice
.
Here’s the implementation:
import { produce } from "immer";
// Custom createSlice Method
export function myCreateSlice(config) {
const { name, initialState, reducers } = config;
// Action Creators
const actions = {};
Object.keys(reducers).forEach((key) => {
actions[key] = function (payload) {
return { type: `${name}/${key}`, payload };
};
});
// Reducer
function reducer(state = initialState, action) {
return produce(state, (draft) => {
if (action.type.startsWith(`${name}/`)) {
const key = action.type.split("/")[1];
const caseReducer = reducers[key];
if (caseReducer) caseReducer(draft, action);
}
});
}
return { name, actions, reducer };
}
Explanation:
Input Config
The function accepts a config object containing:
- name: Slice name (used as prefix for actions).
- initialState: Initial state of this slice.
- reducers: An object with functions to update the state.
Action Creators
- For each reducer function, we generate an action creator.
- Action type is auto-generated in the format:
Example:sliceName/actionName
counter/increment
.
Reducer Function
- Uses Immer (
produce
) to update state immutably. - Checks if the
action.type
matches this slice. - If yes, calls the correct case reducer (
reducers[key]
).
Return Value
The function returns:
- name → slice name
- actions → auto-generated action creators
- reducer → slice reducer function
Key Takeaways:
- This mimics Redux Toolkit’s
createSlice
, showing how actions and reducers are tied together. - The heavy lifting is done by Immer, which allows us to write mutable-style code while keeping immutability.
- With this approach, we don’t need separate
actionTypes.js
,actions.js
, andreducers.js
files.
By building this from scratch, I gained a deeper understanding of Redux Toolkit’s magic and why it simplifies Redux so much.
3. Middleware in Redux
Middleware in Redux acts like a bridge between dispatching an action and the moment it reaches the reducer.
It lets us intercept actions, perform extra logic (like logging, API calls, authentication), and then decide whether to continue or block the action.
How Middleware Works Internally?
- A middleware is essentially a function that wraps
dispatch
. - It takes three arguments:
store
→ gives access togetState
anddispatch
.next
→ passes the action to the next middleware (or reducer if it’s the last).action
→ the dispatched action.
A middleware function looks like this:
const loggerMiddleware = (store) => (next) => (action) => {
console.log("Dispatching:", action);
next(action); // pass action to next middleware/reducer
console.log("Next State:", store.getState());
};
⚠️ Important: If we forget to call
next(action)
, the action will never reach the reducer, and our state will not update. Always ensure we call it unless we intentionally want to stop the action.
Middleware in Redux Toolkit:
Redux Toolkit comes with some default middleware out of the box:
- redux-thunk → allows async logic inside Redux (e.g., API calls).
- serializableCheck → ensures state/actions are serializable.
- immutableCheck → warns against accidental state mutations.
The good part? RTK adds them automatically when we create a store with configureStore
.
Adding Custom Middleware (Logger Example):
We can add our own middleware in configureStore
import { configureStore } from "@reduxjs/toolkit";
import counterReducer from "./counterSlice";
const loggerMiddleware = (store) => (next) => (action) => {
console.log("Action:", action.type);
next(action);
console.log("Updated State:", store.getState());
};
const store = configureStore({
reducer: {
counter: counterReducer,
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(loggerMiddleware),
});
Multiple Middleware:
When we add multiple middleware, they run in the exact order we define.
Think of them as a chain:
middleware1 → middleware2 → middleware3 → reducer
- First,
middleware1
sees the action. - Then passes it to
middleware2
, and so on... - Finally, it reaches the reducer.
In Redux Toolkit, some middleware like redux-thunk
, serializableCheck
, and immutableCheck
are already added by default.
When you add our own middleware (e.g., logger, API handler), it gets chained alongside RTK’s default middleware.
This layering makes middleware powerful for handling responsibilities such as:
- Logging
- Authentication
- Async API calls
- Error handling
const store = configureStore({
reducer: {
// our reducers here
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(loggerMiddleware, authMiddleware),
});
Here:
- Default middleware from RTK (like
redux-thunk
) are added automatically. - Then we concatenate our custom middlewares (
loggerMiddleware
,authMiddleware
). - They run in order:
default → logger → auth → reducer
Why Middleware is Important?
- Handles side effects (API calls, logging, analytics).
- Keeps reducers pure (reducers should only update state).
- Helps in debugging with tools like Redux Logger.
- Scales well for larger apps with complex logic.
Final Thoughts:
- Middleware is one of the superpowers of Redux.
- It allows us to extend Redux’s behavior without touching reducers directly.
- With Redux Toolkit, adding and managing middleware becomes effortless.
- Custom middleware like a logger or API handler makes our app more maintainable, scalable, and debuggable.
4. Selectors in Redux
Selectors are functions that read data from the Redux store.
Instead of directly accessing state.something
everywhere in the app, we create selectors to:
- Encapsulate how state is structured
- Avoid repeating logic
- Make code cleaner and easier to maintain
Example:
// Without selector
const todos = useSelector((state) => state.todos);
// With selector
const selectTodos = (state) => state.todos;
const todos = useSelector(selectTodos);
Why Use Selectors?
- Reusability → Use the same selector in multiple components.
- Maintainability → If the state shape changes, update only the selector, not every component.
- Readability → Components focus only on rendering, not data logic.
- Performance → With memoization, prevent unnecessary re-renders.
createSelector (Reselect):
Redux Toolkit recommends using Reselect’s createSelector
for advanced selectors.
createSelector
allows us to memoize derived data.- It only recalculates when the input state changes, improving performance.
import { createSelector } from "@reduxjs/toolkit";
// Input selectors
const selectTodos = (state) => state.todos;
const selectFilter = (state) => state.filter;
// Memoized selector
const selectVisibleTodos = createSelector(
[selectTodos, selectFilter],
(todos, filter) => {
if (filter === "completed") return todos.filter((t) => t.completed);
if (filter === "active") return todos.filter((t) => !t.completed);
return todos;
}
);
// Usage in component
const visibleTodos = useSelector(selectVisibleTodos);
Why Use createSelector?
- Memoization → Prevents expensive recalculations.
- Performance boost → Component re-renders only if the relevant slice of state changes.
- Clean abstraction → Derived state logic stays in selectors, not inside components.
- Scalability → As apps grow, selectors keep state access consistent and optimized.
5. What’s Next
I’m excited to keep growing and sharing along the way! Here’s what’s coming up:
- Posting new blog updates every 3 days to share what I’m learning and building.
- Diving deeper into Data Structures & Algorithms with Java — check out my ongoing DSA Journey Blog for detailed walkthroughs and solutions.
- Sharing regular progress and insights on X (Twitter) — feel free to follow me there and join the conversation!
Thanks for being part of this journey!
6. Conclusion
Over the last few days, I went beyond the core concepts of Redux and explored the modern way of managing state with Redux Toolkit (RTK).
Here’s what I accomplished:
- Learned how
configureStore
simplifies store creation by handling middleware and DevTools setup automatically. - Explored
createSlice
, the real game-changer in RTK, that combines state, reducers, and actions in one place. - Built my own custom
createSlice
method from scratch, which gave me deeper insights into how Redux Toolkit works under the hood. - Understood the power of middleware — how it intercepts actions, adds extra functionality (like logging & async operations), and why it’s crucial for scalability.
- Practiced selectors and
createSelector
, making state access reusable, optimized, and maintainable.
🔥 The biggest lesson? Redux Toolkit drastically reduces boilerplate and makes state management cleaner, more scalable, and easier to reason about.
This marks another solid step in my Web Development Journey. I now feel more confident in using Redux Toolkit for real-world React projects.
👉 Up next: I’ll be diving into RTK Query, Redux Toolkit’s powerful data-fetching and caching solution, to simplify API calls even further. Stay tuned!
Thanks for reading — and if you’re also learning Redux Toolkit, I hope this blog helps you speed up your journey
Subscribe to my newsletter
Read articles from Ritik Kumar directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Ritik Kumar
Ritik Kumar
👨💻 Aspiring Software Developer | MERN Stack Developer.🚀 Documenting my journey in Full-Stack Development & DSA with Java.📘 Focused on writing clean code, building real-world projects, and continuous learning.