The Power of Redux Toolkit

April 8, 2024

10 min

Managing state in large-scale applications can become challenging. Redux has long been a popular tool for handling complex state management in React applications. With the introduction of Redux Toolkit, managing state has become more efficient, streamlined, and easier to work with.

In this post, we'll explore how to manage CRUD (Create, Read, Update, Delete) operations using Redux Toolkit by building a simple ToDo app. This app will demonstrate how to integrate Redux Toolkit for managing a list of tasks.

What is Redux Toolkit?

Before diving into the example, let's briefly talk about Redux Toolkit. Redux Toolkit is the official, recommended way to write Redux logic. It simplifies the Redux setup process by providing utility functions, such as createSlice, configureStore, and createAsyncThunk, which reduce boilerplate code and improve maintainability.

Redux Toolkit Example

Setting Up Your React Project

Let's start by setting up a new React project with Redux Toolkit. If you don't have a project yet, create one using Create React App:


npx create-react-app redux-todo-app
cd redux-todo-app
npm install @reduxjs/toolkit react-redux

Creating a Redux Slice for ToDos

Redux slices are a convenient way to manage state with reducers and actions in one place. We'll create a slice to manage our ToDo list by importing the createSlice from Toolkit.

Create a new file redux/todos/todoSlice.js:

import { createSlice } from "@reduxjs/toolkit";

const todoSlice = createSlice({
  name: "todos",
  initialState: [],
  reducers: {
    addTodo: (state, action) => {
      state.push(action.payload);
    },
    removeTodo: (state, action) => {
      return state.filter((todo) => todo.id !== action.payload);
    },
    updateTodo: (state, action) => {
      const index = state.findIndex((todo) => todo.id === action.payload.id);
      if (index !== -1) {
        state[index] = action.payload;
      }
    },
    toggleComplete: (state, action) => {
      const todo = state.find((todo) => todo.id === action.payload);
      if (todo) {
        todo.completed = !todo.completed;
      }
    },
  },
});

export const { addTodo, removeTodo, updateTodo, toggleComplete } =
  todoSlice.actions;

export default todoSlice.reducer;

In the above code:

  • addTodo: Adds a new ToDo to the list
  • removeTodo: Removes a ToDo by its ID.
  • updateTodo: Updates an existing ToDo’s details.
  • toggleComplete: Toggles the completed status of a ToDo.

These are what we call reducers, which are collections of functions or methods that update the global state.

Setting Up the Redux Store

Now let's configure the Redux store to use the todoSlice. Open or create a new file app/store.js:

import { configureStore } from "@reduxjs/toolkit";
import todoReducer from "../redux/todos/todoSlice";

export const store = configureStore({
  reducer: {
    todos: todoReducer,
  },
});

Store: You can think of it as a storage area where data that needs to be kept globally is held.

Wrapping the App with Redux Provider

Next, we need to wrap our main app component with the Provider component from react-redux to make the Redux store available to all components in the app.

In src/index.js, do the following:

import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import { store } from "./app/store";
import App from "./App";

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById("root"),
);

Building the ToDo App UI

Now that we have set up Redux, let’s create the ToDo app UI. The app will have:

  • An input field to add new tasks.
  • A list of tasks displaying their names and completion status.
  • Buttons to update and delete tasks.

In src/App.js, create the ToDo app UI:

import React, { useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import {
  addTodo,
  removeTodo,
  updateTodo,
  toggleComplete,
} from "./features/todos/todoSlice";

function App() {
  const [todoText, setTodoText] = useState("");
  const todos = useSelector((state) => state.todos);
  const dispatch = useDispatch();

  const handleAddTodo = () => {
    if (todoText.trim()) {
      dispatch(
        addTodo({
          id: Date.now(),
          text: todoText,
          completed: false,
        }),
      );
      setTodoText("");
    }
  };

  const handleToggleComplete = (id) => {
    dispatch(toggleComplete(id));
  };

  const handleRemoveTodo = (id) => {
    dispatch(removeTodo(id));
  };

  const handleUpdateTodo = (id) => {
    const newText = prompt("Update Todo text:");
    if (newText) {
      dispatch(
        updateTodo({
          id,
          text: newText,
          completed: false,
        }),
      );
    }
  };

  return (
    <div className="App">
      <h1>ToDo App with Redux Toolkit</h1>
      <input
        type="text"
        value={todoText}
        onChange={(e) => setTodoText(e.target.value)}
        placeholder="Enter a new todo"
      />
      <button onClick={handleAddTodo}>Add Todo</button>

      <ul>
        {todos.map((todo) => (
          <li
            key={todo.id}
            style={{ textDecoration: todo.completed ? "line-through" : "none" }}
          >
            <span onClick={() => handleToggleComplete(todo.id)}>
              {todo.text}
            </span>
            <button onClick={() => handleUpdateTodo(todo.id)}>Edit</button>
            <button onClick={() => handleRemoveTodo(todo.id)}>Delete</button>
          </li>
        ))}
      </ul>
    </div>
  );
}

export default App;

Explanation:

  • useSelector: This hook allows us to access the Redux state.
  • useDispatch: This hook allows us to dispatch actions to the Redux store.
  • We have functions to add, remove, update, and toggle completion status of tasks, with each action calling the corresponding Redux action.

Conclusion

In this note, we built a simple ToDo app with Redux Toolkit that manages tasks using CRUD operations. Redux Toolkit made it much easier to set up and manage the state compared to traditional Redux. By using createSlice and the various utility functions provided by Redux Toolkit, we reduced boilerplate and improved the readability of the code.

Now, you can use the same approach in your own apps to manage complex state, simplify logic, and avoid repetitive code.

I hope this article is helpful to you down the road!