2025-08-22 — By Akshay · 8 min read read
State management is one of the most common challenges in React applications. As your app grows, you often need a centralized store where multiple components can access and update shared data. This is where Redux comes into play.
In this guide, we will walk through how to add Redux Toolkit (with Redux Toolkit and Redux Persist) into a React.js application — step by step.
Redux is a state management tool that helps you manage global state in predictable ways. Instead of passing props through multiple levels of components, Redux provides:
With Redux Toolkit, the setup becomes much simpler compared to the old boilerplate-heavy Redux.
Let’s assume you already have a React project set up (using Vite or Create React App). We’ll integrate Redux step by step.
npm install @reduxjs/toolkit react-redux redux-persist redux-logger
A clean folder structure helps scale your app:
src/
redux/
store.js
rootReducer.js
features/
auth/
slice.js
todos/
slice.js
store.js
→ Configures Redux store.rootReducer.js
→ Combines all feature slices.features/
→ Each domain (auth, todos, users, etc.) has its own slice.Each feature has its own slice using Redux Toolkit’s createSlice
.
Example: features/todos/slice.js
import { createSlice } from "@reduxjs/toolkit";
const todoSlice = createSlice({
name: "todos",
initialState: [],
reducers: {
addTodo: (state, action) => {
state.push({ id: Date.now(), text: action.payload, completed: false });
},
toggleTodo: (state, action) => {
const todo = state.find((t) => t.id === action.payload);
if (todo) todo.completed = !todo.completed;
},
removeTodo: (state, action) => {
return state.filter((t) => t.id !== action.payload);
},
},
});
export const { addTodo, toggleTodo, removeTodo } = todoSlice.actions;
export default todoSlice.reducer;
Example: redux/rootReducer.js
import { combineReducers } from "redux";
import todoReducer from "./features/todos/slice";
import authReducer from "./features/auth/slice";
const rootReducer = combineReducers({
todos: todoReducer,
auth: authReducer,
});
export default rootReducer;
Example: redux/store.js
import { configureStore } from "@reduxjs/toolkit";
import { persistStore, persistReducer } from "redux-persist";
import storage from "redux-persist/lib/storage";
import logger from "redux-logger";
import rootReducer from "./rootReducer";
// Persist config
const persistConfig = {
key: "root",
storage,
};
// Create persisted reducer
const persistedReducer = persistReducer(persistConfig, rootReducer);
// Configure store
export const store = configureStore({
reducer: persistedReducer,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({ serializableCheck: false }).concat(logger),
});
export const persistor = persistStore(store);
Wrap your app with Provider
and PersistGate
so Redux is available globally.
Example: src/App.js
import React from "react";
import { Provider } from "react-redux";
import { PersistGate } from "redux-persist/integration/react";
import { store, persistor } from "./redux/store";
import TodoApp from "./components/TodoApp";
function App() {
return (
<Provider store={store}>
<PersistGate loading={<div>Loading...</div>} persistor={persistor}>
<TodoApp />
</PersistGate>
</Provider>
);
}
export default App;
Now you can use hooks like useSelector
and useDispatch
to read and update state.
Example: components/TodoApp.js
import React, { useState } from "react";
import { useSelector, useDispatch } from "react-redux";
import { addTodo, toggleTodo, removeTodo } from "../redux/features/todos/slice";
export default function TodoApp() {
const [input, setInput] = useState("");
const todos = useSelector((state) => state.todos);
const dispatch = useDispatch();
return (
<div>
<h2>Todo App</h2>
<input value={input} onChange={(e) => setInput(e.target.value)} />
<button
onClick={() => {
dispatch(addTodo(input));
setInput("");
}}
>
Add
</button>
<ul>
{todos.map((todo) => (
<li key={todo.id}>
<span
style={{
textDecoration: todo.completed ? "line-through" : "none",
}}
onClick={() => dispatch(toggleTodo(todo.id))}
>
{todo.text}
</span>
<button onClick={() => dispatch(removeTodo(todo.id))}>❌</button>
</li>
))}
</ul>
</div>
);
}
Normally, Redux state resets when the page reloads. Redux Persist saves the store in localStorage
(or another storage engine), so your data stays even after refresh.
Use cases:
Redux may seem complex at first, but with Redux Toolkit, setting it up is much easier. By combining it with Redux Persist, you ensure that your app maintains state across sessions.
👉 For small apps, React Context might be enough. But if your app grows with authentication, multiple entities, caching, or complex workflows, Redux is still the most reliable choice.