DEV Community

Cover image for Creating a To-do list app using Redux Toolkit
Yosra
Yosra

Posted on • Originally published at hashnode.com

Creating a To-do list app using Redux Toolkit

Introduction

Redux Toolkit is a react package for state management built on redux. In this tutorial, I’ll explain how to use the Redux toolkit by building a small to do list app that keeps its state using Redux Toolkit. I personally prefer redux toolkit over regular redux because it is much easier to use. The creators of the package intended to make it the default way to write Redux logic. They eliminated a bunch of boilerplate code and made redux state management easier to create.

The UI

I won’t be focusing on the UI in this tutorial, so I made this github repository that you can clone that has the UI already created for you. (link)

Now we have the UI that looks like this:

initial UI

Using Redux Toolkit

Now that we have our UI ready, you’ll see that I’ve hard-coded the to-do list items. Our goal in this part of the article is to make the user able to add and remove to-dos from the list.

Installing Redux Toolkit

After cloning the repository above and installing packages, you should install redux toolkit and react-redux using the following command:

npm install @reduxjs/toolkit react-redux
Enter fullscreen mode Exit fullscreen mode

Creating the Todo Slice

The slice is the component that keeps track of the app’s state. In it, You should define the initial state and the functions that will be needed to change the state when necessary.

  1. To build the slice, you need to import createSlice . Which is a function inside the Redux Toolkit package.

    import { createSlice } from "@reduxjs/toolkit";
    
  2. Next, define your initial state. In this case, two variables will be part of the state:

    • count : an integer that keeps track of the number of to-dos in the app (initially equals 0)
    • todos: a list that contains all the todos that the user added (initially empty)
    const initialState = {
      count: 0,
      todos: [],
    };
    
  3. Now, use the createSlice function that was imported. It takes three arguments:

    • Name of the slice (in this case todo)
    • Initial state (defined above)
    • reducers (the functions that will be used to change the state values)
    export const todoSlice = createSlice({
      name: "todo",
      initialState,
      reducers: {
        addTodo: (state, action) => {
          const todo = {
            id: Math.random() * 100,
            text: action.payload,
          };
          state.todos.push(todo);
          state.count += 1;
        },
        removeTodo: (state, action) => {
          state.todos = state.todos.filter((todo) => todo.id !== action.payload);
          state.count -= 1;
        },
      },
    });
    

    Here, two functions were added, one is addTodo and the other is removeTodo:

- `addTodo` → creates a new todo item using the text passed in the argument and assigns a random id to it. It pushes the created todo to the todos list and increases the count value.
- `removeTodo` → removes the todo item using the id given in the arguments from the todos list and decrements the count value.

Note that to get the argument passed to the function, we use `action.payload`. If you need more than one argument, you can send the arguments as one object and access it using the payload. For example:
Enter fullscreen mode Exit fullscreen mode
```jsx
action.payload.variable
```
Enter fullscreen mode Exit fullscreen mode
  1. Export the functions to be able to use them in the app. You can do so by accessing todoSlice.actions.

    export const { addTodo, removeTodo } = todoSlice.actions;
    
  2. Finally, export the reducer. This will be used in the store to be able to provide the state to the app (see next section)

    export default todoSlice.reducer;
    

Full TodoSlice code

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

const initialState = {
  count: 0,
  todos: [],
};

export const todoSlice = createSlice({
  name: "todo",
  initialState,
  reducers: {
    addTodo: (state, action) => {
      const todo = {
        id: Math.random() * 100,
        text: action.payload,
      };
      state.todos.push(todo);
      state.count += 1;
    },
    removeTodo: (state, action) => {
      state.todos = state.todos.filter((todo) => todo.id !== action.payload);
      state.count -= 1;
    },
  },
});

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

export default todoSlice.reducer;
Enter fullscreen mode Exit fullscreen mode

Creating the store

The store is the component that you provide to the redux provider that makes it aware of the existence of the state.

  1. Create a folder in your src folder called “store” and create an index.js file inside

    create a store folder

  2. Add the following code to the file. This will make the store listen to our reducer and that’s how we’ll be able to access the variables and the functions inside our slice. We’ll name our reducer “todo”

    import { configureStore } from "@reduxjs/toolkit";
    import todoReducer from "../features/todoSlice";
    
    const store = configureStore({
      reducer: {
        todo: todoReducer,
      },
    });
    
    export default store;
    

Adding the store to our app.

To provide the store to the app, use the normal redux provider.

In your index.js file add the following code:

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

ReactDOM.render(
  <Provider store={store}>
    <React.StrictMode>
      <App />
    </React.StrictMode>
  </Provider>,
  document.getElementById("root")
);
Enter fullscreen mode Exit fullscreen mode

Using the State

To use the state that was just created in the app, import useSelector and useDispatch from redux. Also, import addTodo and removeTodo from the slice file.

  1. Add the following imports to your App.js file:

    import { useSelector, useDispatch } from "react-redux";
    import { addTodo, removeTodo } from "./features/todoSlice";
    
  2. In the App function, get both the count and todo states using useSelector. Note that to access any of those states, you should access the reducer name that was specified in the store file like this:

    const count = useSelector((state) => state.todo.count);
    const todos = useSelector((state) => state.todo.todos);
    
  3. Extract the dispatch function that will be used to run addTodo and removeTodo functions.

    const dispatch = useDispatch();
    
  4. Write handleAddTodo function that will be triggered when the form is submitted.

    const handleAddTodo = (e) => {
        e.preventDefault();
        dispatch(addTodo(input));
    };
    

    Calling e.preventDefault() is necessary because the form’s default action is to refresh the page after submitting. If you let it refresh the page, all our state will be reset.

  5. For the handleTodoDone function, it will be triggered it when the user clicks on a todo item. The id of the todo will be used to identify which todo is removed.

    const handleTodoDone = (id) => {
        dispatch(removeTodo(id));
    };
    
  6. In the form element, add an onSubmit action that points to handleAddTodo

    <form className="App-form" onSubmit={handleAddTodo}>
    
  7. To show the list of to-dos, map the todos list to the TodoItem component and pass the id to the key and id attributes. In addition, pass the text of the todo and the handleTodoDone function.

    If the count is equal to 0, “No todos” will be displayed to the user.

    <div className="Todos">
            {count > 0 &&
              todos.map((todo) => (
                <TodoItem
                  key={todo.id}
                  text={todo.text}
                  id={todo.id}
                  onCheck={handleTodoDone}
                />
              ))}
            {count === 0 && <p>No todos</p>}
    </div>
    
  8. In the TodoItem file, add a function that listens to clicking on the item

    .

    Full TodoItem.js file:

    import "./TodoItem.css";
    
    const TodoItem = (props) => {
      const deleteTodo = () => {
        props.onCheck(props.id);
      };
      return (
        <div className="todo" onClick={deleteTodo}>
          <input type="checkbox"></input>
          <label>{props.text}</label>
        </div>
      );
    };
    
    export default TodoItem;
    
  9. Your app.js file should look like this now:

    import { useState } from "react";
    
    import "./App.css";
    
    import TodoItem from "./components/TodoItem";
    import { useSelector, useDispatch } from "react-redux";
    import { addTodo, removeTodo } from "./features/todoSlice";
    
    function App() {
      const [input, setInput] = useState("");
    
      const count = useSelector((state) => state.todo.count);
      const todos = useSelector((state) => state.todo.todos);
      const dispatch = useDispatch();
    
      const handleAddTodo = (e) => {
        e.preventDefault();
        dispatch(addTodo(input));
      };
    
      const handleTodoDone = (id) => {
        dispatch(removeTodo(id));
      };
      return (
        <div className="App">
          <h1>TODO List</h1>
          <form className="App-form" onSubmit={handleAddTodo}>
            <input type="text" onInput={(e) => setInput(e.target.value)} />
            <button type="submit">+</button>
          </form>
          <div className="Todos">
            {count > 0 &&
              todos.map((todo) => (
                <TodoItem
                  key={todo.id}
                  text={todo.text}
                  id={todo.id}
                  onCheck={handleTodoDone}
                />
              ))}
            {count === 0 && <p>No todos</p>}
          </div>
        </div>
      );
    }
    
    export default App;
    
  10. Final Output

    output.gif

    Conclusion

    That’s it for the redux toolkit tutorial, you can find the full code here. I personally prefer redux toolkit over regular redux because it lessens the boiler plate code a lot. No one likes to write the same code over and over again right? I hope you enjoyed this tutorial, your feedback is more than welcome! In the next tutorial, I’ll be connecting this to-do list app to firebase so stay tuned!

Top comments (2)

Collapse
 
sadullah profile image
Sadullah TANRIKULU

Thanks for blog, but I see most of the redux blogs, even chatgpt prompts, add and delete functions are demonstrated by bloggers. Frankly I got it, update function in redux has a curve because of can not modifying state. It requires middleware such as thunk or saga. I try to understand redux for two days, slice, reducers, store, useDispatch, useSelector ... I believe I need a while then I can achieve it. Thanks again.

Collapse
 
eric_99 profile image
Eric Haal

with the help of immer lib, react toolkit can modify the state directly.