Mastering State Management: A Comprehensive Guide to Redux Toolkit in React - Build a To-Do App

As applications grow in complexity, managing state becomes a crucial aspect of development. Redux, a JavaScript library commonly used with React, offers a predictable state container that greatly simplifies state management.
Redux Toolkit, an official package endorsed by the Redux team, further enhances the development experience by providing a set of utilities to streamline common Redux patterns and make state management more efficient.
We will delve into the core concepts of Redux Toolkit, exploring how it simplifies state management, organizes code, and enhances developer productivity. Throughout the article, I will walk you through practical examples, demonstrating the implementation of key features such as the Redux store, actions, reducers, and the use of the Redux Toolkit to streamline your development workflow.
In this guide, I will take you through building a To-Do App with the React and Redux Toolkit. This will help you understand the key concepts of the Redux Toolkit and equip you with hands-on experience. If you want to get much of this guide, code along on your favorite editor.
Prerequisites
Before diving into the guide on Redux Toolkit, it's essential to ensure that you have a foundational understanding of certain prerequisites. Here are the suggested prerequisites for the guide:
Basic Knowledge of JavaScript
Familiarity with React
Familiarity with Redux or Context API
Node.js and npm Installed
According to the docs, the Redux Toolkit was created to help address three common concerns about Redux.
Configuring a Redux store is too Complicated
You have to add a lot of packages to get Redux to do anything useful.
Redux requires too much boilerplate code.
Redux Toolkit makes it easier to write good Redux applications and speeds up development, by taking the recommended best practices, providing good default behavior, catching mistakes, and allowing you to write simpler code.
Redux Toolkit can be added at the start of a new project, or used as part of an incremental migration in an existing project.
Now to get started, create a new React app with the following command and open it in your favorite editor.
//npm
npm create vite@latest
//yarn
yarn create vite
Optional
I have not focused on styling the app but in case you want yours to look better, you can go ahead and style it. To use Tailwind CSS, Go to Tailwind Docs and check for installation. Under the installation select Frameworks Guides on which you will select the framework you are using, Vite in this case. Follow the simple instructions to configure Tailwind CSS, and you will be ready.
Install Redux Toolkit
You will need the following to get started with Redux.
//npm
npm install @reduxjs/toolkit
//yarn
yarn add @reduxjs/toolkit
You will also need the React bindings
//npm
npm install react-redux
//yarn
yarn add react-redux
We will create a basic To-Do App where a user can add and remove tasks. To get started, we will follow several steps.
Create the Store
A Store is the main, central bucket that stores all the states of an application. It should be considered and maintained as a single source of truth for the state of the application.
On the root of your project create a app directory and create a Store.js file where we will configure the Store.
Add the following code to the Store.js file
import { configureStore } from '@reduxjs/toolkit';
export const store = configureStore({
reducer: //eventually add a reducer
})
The code imports the configureStore function from the Redux Toolkit package. The reducer is a function that specifies how the state should change in response to different actions.
Write Reducers
A common convention is creating a folder named features. You can name it whatever you want. In features we might have different types of features such as authentication features, products, chats, etc. So for this project, we will have a todo feature and, therefore, create todo folder.
Now, in the todo folder, create a todoSlice.js. In this file, we will create the Slice. What is a Slice? Let's first define some terms here for easier understanding.
Actions. Actions are simply JavaScript objects that have a type with a payload of data illustrating exactly what is happening. They are the only way your application can interact with the store.
Reducers. A Reducer is a function that takes in the current state of an application and action as arguments and returns a new state based on the action.
Slice. A slice is a collection of Redux reducer logic and actions for a single feature in your app, typically defined together in a single file.
Write the following code in the todoSlice.js
import { createSlice, nanoid } from "@reduxjs/toolkit";
//create an initial state.
//for the todo it will be an empty array
const initialState = {
todos: [],
};
//create the slice.. the slice is an object consisting of a name, initialState and reducers.
//the reducers are responsible for updating the store
export const todoSlice = createSlice({
name: "todo",
initialState,
reducers: {
addTodo: (state, action) => {
const todo = {
id: nanoid(),
text: action.payload,
};
state.todos.push(todo);
},
removeTodo: (state, action) => {
state.todos = state.todos.filter((todo) => todo.id !== action.payload);
},
},
});
//export each method in the reducers as actions
export const {addTodo, removeTodo} = todoSlice.actions
//export the entire reducer because the reducer has to be wired up withe the store.
export default todoSlice.reducer;
Initial State: The code starts by defining an initial state with an empty array for
todos. This serves as the starting point for the state managed by the Redux store.Create a Slice: The
createSlicefunction creates a Redux slice named "todo." This slice includes the initial state and reducers, which are functions responsible for updating the store based on dispatched actions.Reducers: The
reducersobject contains two functions:addTodoandremoveTodo. These functions define how the state should be updated in response to corresponding actions. For example,addTodoadds a new todo with a unique ID to the state, andremoveTodofilters out a todo based on its ID.Action Creators: The
createSlicefunction automatically generates action creators for each reducer. Action creators likeaddTodoandremoveTodoare exported and can be used to dispatch actions in the application.Exporting the Reducer: The entire reducer generated by the slice is exported. This reducer is crucial for connecting with the Redux store during the store configuration phase.
this code efficiently creates a Redux slice for managing todos. It defines the initial state, reducers to handle state changes, and exports both individual action creators and the reducer for integration into the Redux store.
You have noticed that we have exported Reducer. This is necessary because it has to be wired with the Store. Let’s go back to the Store.js file and connect the Store and the Reducer.
Store.js
import { configureStore } from '@reduxjs/toolkit';
import todoReducer from '../features/todo/todoSlice'
export const store = configureStore({
reducer: todoReducer
})
The Store is now connected to the Reducer.
Now, in the next steps, we will write components where we will add, remove, and display the to-do’s.
Add to-do
To add a to-do, we have to collect the to-do from the user input. Then send the to-do to the Store.
To send the to-do to the Store, We use a dispatch function with the useDispatch hook.
The dispatch function as it is named, will dispatch actions. The action in this case is addTodo that we exported from the todoSlice.js file.
Begin by creating a folder named components. In the components folder, create a file named addTodo.jsx. In this file, we write the logic to add a todo. The code below will help you understand.
import React, { useState } from "react";
import { addTodo } from "../features/todo/todoSlice";
import { useDispatch } from "react-redux";
const AddTodo = () => {
// State to manage the input value
const [input, setInput] = useState("");
// Redux dispatch function to trigger actions
const dispatch = useDispatch();
// Function to handle the submission of a new todo
const addTodoHandler = (e) => {
// Prevent the default form submission behavior
e.preventDefault();
// Dispatch the addTodo action with the current input value
dispatch(addTodo(input));
// Clear the input field after submission
setInput("");
};
return (
// Form element for adding a new todo
<form className="space-x-3 mt-12">
{/* Label for the input field */}
<label htmlFor="Task">Task : </label>
{/* Input field for entering the todo text */}
<input
type="text"
className="bg-gray-800 rounded border border-gray-700 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-900 text-base outline-none text-gray-100 py-1 px-3 leading-8 transition-colors duration-200 ease-in-out"
placeholder="Enter to do.."
value={input}
onChange={(e) => setInput(e.target.value)}
/>
{/* Button to submit the new todo */}
<button
type="submit"
className="text-white m-3 border border-slate-700"
onClick={addTodoHandler}
>
submit
</button>
</form>
);
};
// Export the AddTodo component
export default AddTodo;
Simply, This code defines a React functional component for adding a new todo. It uses local state to manage the input value, Redux Toolkit to dispatch an action (addTodo), and includes a form with an input field and a submit button. The addTodoHandler function handles form submission by dispatching the addTodo action and clearing the input field.
At this point, we have managed to add a to-do in the Store. Now we have to access it and display it on the UI.
To access the to-do’s in the Store, We have to make the Store accessible. By this I mean, the store should be available to be accessible by components that need the data in it.
We achieve this by wrapping the entire App with the Provider component.
The store prop passed to the Provider is the Redux store instance, which holds the application's state.
Go to the main.jsx file and modify it by importing the Provider from react-redux and wrapping the App with it.
import React from 'react'
import { Provider } from 'react-redux'
import ReactDOM from 'react-dom/client'
import App from './App.jsx'
import './index.css'
import { store } from './app/store.js'
//to make it possible for all components be aware of the store and be able
//to access the data, wrap the entire App with Provider.
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>,
)
With that, we have made it possible for all components to be aware of the Store and be able to access the data in the Store. And as you already know, the data is the to-do’s.
Accessing the to-do’s
We have already learned how to save the to-do’s in the Store via the dispatch function. Now let’s learn how to access the data in the Store.
To access the data in the Store we use a hook called useSelector.
The useSelector takes in a function argument that returns the part of the state that you want.
The useSelector will help us read data from the Store.
The following code demonstrates how it does so. It also includes the logic for removing a todo from the Store. To remove a to-do, you simply dispatch the removeTodo action using the dispatch function.
Create a Todos.jsx file in the components folder and add the following code.
import React from "react";
import { useSelector } from "react-redux";
import { Trash2 } from "react-feather";
import { removeTodo } from "../features/todo/todoSlice";
import { useDispatch } from "react-redux";
// Component for displaying and managing todos
const Todos = () => {
const dispatch = useDispatch(); // Redux dispatch function
const todos = useSelector((state) => state.todos); // Retrieve todos from the Redux store state
return (
<>
{/* Section heading */}
<div>
<h3>My Tasks</h3>
</div>
{/* List of todos */}
<ul className="border border-white-700 m-4">
{/* Map through todos and render each todo */}
{todos.map((todo) => {
return (
<div className="flex p-4 justify-evenly border border-slate-700 m-2" key={todo.id}>
{/* Display the todo text */}
<h3>{todo.text}</h3>
{/* Trash icon for removing the todo, with onClick dispatching removeTodo action */}
<Trash2
size={20}
className="cursor-pointer"
onClick={() => dispatch(removeTodo(todo.id))}
/>
</div>
);
})}
</ul>
</>
);
};
export default Todos;
Explanation:
Redux Hooks:
useDispatch: Hook to get access to the Redux dispatch function.useSelector: Hook to extract data from the Redux store state.
Component Structure:
- The
Todoscomponent renders a heading and a list of todos.
- The
Redux State Access:
todosis obtained from the Redux store usinguseSelector. It retrieves an array of todos from the Redux store state.
Todo Rendering:
- The
todosarray is mapped, and each todo is displayed with its text and a Trash2 icon imported from react-feather.
- The
Remove Todo Functionality:
- Clicking the Trash2 icon triggers the
removeTodoaction by dispatching it with the to-do's ID. This action is handled by the Redux reducer in thetodoSlice.jsfile to update the state and remove the corresponding todo.
- Clicking the Trash2 icon triggers the
In summary, the Todos component retrieves and displays todos from the Redux store state. Users can remove todos by clicking the Trash2 icon, which dispatches a Redux action for todo removal.
Load the addTodo.jsx and Todos.jsx files in the App
import "./App.css";
import AddTodo from "./components/AddTodo";
import Todos from "./components/Todos";
function App() {
return (
<>
<AddTodo />
<Todos />
</>
);
}
export default App;
Redux Dev Tools
Redux provides us with a Dev Tools extension to view the Redux store and action history: The DevTools extension allows you to inspect the contents of the Redux store and record all actions that have been dispatched. This is useful for debugging purposes, as you can see the condition of the store and what steps have been dispatched.
The image below showcases what our To-Do app looks like and how to inspect the contents of the Redux Store with the Redux Tool Kit.

And that’s it, friend!
Conclusion
We've explored the fundamental concepts of Redux Toolkit, discovering how it simplifies state management and boosts the efficiency of React applications. By implementing actions, reducers, and leveraging the Redux Toolkit, you're now equipped with a powerful toolset for maintaining a scalable and organized codebase. I appreciate your time invested in mastering these concepts.
For more insightful content and to stay updated on the latest developments in web development and state management, consider subscribing to my newsletter. Receive regular updates, tips, and advanced techniques straight to your inbox, ensuring you stay at the forefront of React best practices. Thank you for joining me on this learning journey, and I look forward to sharing more valuable insights with you in the future. Subscribe now and stay ahead in your development endeavors!



