DEV Community

Cover image for CRUD Todo App - React and Redux-toolkit
Peter Ogbonna
Peter Ogbonna

Posted on

CRUD Todo App - React and Redux-toolkit

Introduction

We’ll make a simple Todo-list app that performs all four CRUD (Create, Read, Update, Delete) operations- on the front-end, using React and Redux-Toolkit: live link

If you are familiar with Redux you can jump straight to the Implementation

What to get in this article:

Why Redux?

Redux is a state management library, that helps centralize your application’s state and logic. It comes with a DevTool which makes it easy to trace when, where, why and how your application’s state changed.
It works with any UI layer and popularly used with Reactjs.
And you might be wondering, what’s the difference between Redux and Redux-Toolkit, let’s take a look at that.

Redux vs ReduxToolkit

Redux is the core library for managing application state, while Redux-Toolkit is a companion library that provides additional utilities for working with Redux. You can use Redux on its own, but many developers find that Redux-Toolkit makes it even easier and more efficient to work with Redux even the Redux team recommends Redux-Toolkit as the approach for writing Redux logic, because it is a newer library that provides a set of utilities for working with Redux. It provides a simpler API for common tasks, as well as performance optimizations that can help to improve the performance of your application for example, the library uses Immer under the hood to provide immutable updates to the store, which can help to avoid unnecessary re-renders. Redux-Toolkit comes with functions for creating and managing Redux stores, as well as middleware for handling asynchronous actions, this can help to make your code more organized and maintainable.
Simply put, Redux-Toolkit is designed to make it easier and more efficient to write Redux applications

How Redux works

In Redux, you basically create an action and dispatch the action using the usedispatch hook.
Redux provides a predictable state container that helps manage the state of your application in a centralized manner, a store. A “store” is a container that retains your application’s global state, each component can access the store without having to send down props from one component to another.

The five major terminologies in Redux:

In major Redux applications you’ll find these major components working hand in hand. I will give brief explanation of each of these terminologies, it might not be clear enough, but follow through, while building our app it will become clearer. We’ll briefly discuss the main terminologies in a typical Redux app

  • Store: A single source of truth that holds the entire state tree of your application.

  • Actions: Actions are the medium used to send data from your application to your Redux store, they are plain JavaScript objects that represent an intention to change the state, they contain a type property that describes the action as well as a payload (an additional data necessary to carryout the action) to update the state.

  • Reducers: Reducers are Pure functions that take the current state and an action and return a new state. They should not modify the current state directly, but instead return a new state object.

  • Dispatch: To update the state, an action must be dispatched to the store using the `dispatch` function. This triggers the store to call the corresponding reducer function with the current state and the dispatched action. Just think of the `dispatch` function as `setState`

  • Selector: To access data from the store. We do this by using the `useSelector` hook from react-redux

Just think of Dispatch and Selector as

const [name, setName] = useState("Grey");

// Dispatch (update the state)
setName("Dan");

// Selector (get the state)
console.log(name) 

Enter fullscreen mode Exit fullscreen mode

Implementation

Installation

To get started, you only need two npm packages
npm install @reduxjs/toolkit react-redux

Create a Redux Store

src/redux/store.js

import { configureStore } from '@reduxjs/toolkit';

export const store = configureStore({
  reducer:{},
});
Enter fullscreen mode Exit fullscreen mode

Add the Redux Store to Index.js parent file

src/index.js

import { Provider } from 'react-redux';
import { store } from './redux/store';

<Provider store={store}>
   <App />
</Provider>
Enter fullscreen mode Exit fullscreen mode

When done importing these necessary components, ensure to wrap your component with the provider and include the store you added recently, this enables your whole app to access the Redux store

Create a Slice

Of course, Redux- Toolkit introduced slice. A slice is a part of the application’s state that can be updated independently. It contains a set of actions and a reducer that handles those actions. Think of a slice as a certain feature you want to apply different action to it, on different occasions, now each action in the feature (slice) uses the same state, having initial value of initialState. In this app, I have 3 actions running in the same slice. All the actions reside in the reducers object

src/redux/reducer.js

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

const initialState = [];

const todoReducer = createSlice({
  name: 'todos',
  initialState,
  reducers: {};
});

export const reducer = todoReducer.reducer;

Enter fullscreen mode Exit fullscreen mode

With this, we can go ahead to add our slice Reducers to the store in src/redux/store.js

import { configureStore } from '@reduxjs/toolkit';
+ import { reducer } from './reducer';

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

Enter fullscreen mode Exit fullscreen mode

This is the basic configuration needed using Redux-toolkit, we are ready to go Redux baby!
Let’s go about creating actions in src/redux/reducer.js


To Add Note

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

const initialState = [];

const todoReducer = createSlice({
  name: 'todos',
  initialState,
  reducers: {
+    // Adding new todo
+   addTodo: (state, action) => {
+      return [...state, action.payload];
+    },
  },
});

+ export const { addTodo} = todoReducer.actions;
export const reducer = todoReducer.reducer;

Enter fullscreen mode Exit fullscreen mode

The state is the current state, with initial value of initialState, action.payload is the value passed into the addTodo function in src/components/TextField/TextField.jsx
We use our actions to work with the store with the help of the useDispatch hook from react-redux, this is how we Dispatch as explained earlier

src/components/TextField/TextField.jsx

import { addTodo } from '../../redux/reducer';
import { useDispatch } from 'react-redux';
const dispatch = useDispatch();

dispatch(
    addTodo({
    id: Math.floor(Math.random() * 100),
    item: text,
})

Enter fullscreen mode Exit fullscreen mode

The addTodo action takes in the value passed in, which is an object of two key: value pairs and adds it to the array, hence updating the state. Therefore making the state an array of objects with two keys: id and text.


To Delete Note:

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

const initialState = [];

const todoReducer = createSlice({
  name: 'todos',
  initialState,
  reducers: {
    // Adding new todo
addTodo: (state, action) => {
     return [...state, action.payload];
   },
+ deleteTodo: (state, action) => {
+      return state.filter((item) => {
+        return item.id !== action.payload;
+      });
+    },
  },
});

+ export const { addTodo, deleteTodo} = todoReducer.actions;
export const reducer = todoReducer.reducer;

Enter fullscreen mode Exit fullscreen mode

The deleteTodo function checks for a matching id to the one passed in through our dispatch function, to update the state, while removing the matched id from the array
Here, the action.payload is id
from src/components/Note/Note.jsx

import { deleteTodo } from '../../redux/reducer';
import { useDispatch } from 'react-redux';
const dispatch = useDispatch();
dispatch(deleteTodo(id));
Enter fullscreen mode Exit fullscreen mode

Update Note:

updateTodo: (state, action) => {
      return state.map((todo) => {
      const item = todo.item;
      if (todo.id === action.payload.id) {
         return {
            ...todo,
            [item]: action.payload.text,
          };
        }
        return todo;
      });
    },
Enter fullscreen mode Exit fullscreen mode

The updateTodo function updates the value of a selected item from the array of objects(different list items), then updates it’s item value by the text.

This function takes an object of two key value pairs as seen in src/components/Note/Note.jsx

import {updateTodo } from '../../redux/reducer';
updateTodo({ id, text: inputRef.current.value });
Enter fullscreen mode Exit fullscreen mode

The id is a numeric value used to identify an item from the list, the text is a string used to update the text of the identified item.

Access the state

This is where selector comes in, as explained earlier we use it by using the useSelector hook from react-redux. It helps us extract data from the Redux store state, using a selector function

import { useSelector } from 'react-redux';
import Note from '../Note/Note';

function ShowTodo() {
  const list = useSelector((state) => state);
  return (
    <>
      {list &&
        list.map((item) => (
          <Note text={item.item} key={item.id} id={item.id} />
        ))}
    </>
  );
}

export default ShowTodo;
Enter fullscreen mode Exit fullscreen mode

And that is how we access data from our store.
Hope you got value!

Helpful Links

Top comments (0)