Srikanth Technologies

Todos using React and Redux

In this blog, we understand how to store todos application state using Redux.

If you are new to Redux, please read my blog on Using Redux in React first and then come to this.

I assume you already created a simple React application and also installed libraries related to Redux - redux, redux-react and @reactjs/toolkit - using npm install. If not, please refer to my blog Using Redux in React for details.

Todos Application

We store all files related to this application in src folder. It is deliberately done to simplify the process.

Here are the files related to this application:

Create Constants - todosActions.js

This file contains constants that represent actions related to todos.

export const ADD_TODO = 'add'
export const COMPLETE_TODO = 'complete'

Creating Reducer - todosReducer.js

It is the reducer that performs all operations.

It supports two operations - ADD_TODO and COMPLETE_TODO, which are specified in todosActions.js.

import { ADD_TODO, COMPLETE_TODO } from './todosActions'

function getNextId(todos) {
    // find out id for last items and add 1 to it 
    if (todos.length === 0)
        return 1;

    return todos[todos.length - 1].id + 1
}

export default function todoReducer(state = { todos: [{ id: 1, text: "Decide what to do" }] }, action) {
   
    switch (action.type) {
        case ADD_TODO:
            let nextId = getNextId(state.todos)              // get next id for todo
            let todo = { id: nextId, text: action.text }     // create a new todo with given text
            return { todos: [...state.todos, { ...todo }] }  // return new state after adding todo 
        case COMPLETE_TODO:
            // remove todo with the given id 
            let newTodos = state.todos.filter(t => t.id !== action.id)
            return { todos: [...newTodos] }
        default:
            return state;
    }
}

Creating Store – store.js

Store is created using configureStore() function of @reduxjs/toolkit library.

Store is associated with todosReducer so that state related to this store is managed by todosReducer.

import { configureStore } from '@reduxjs/toolkit'
import todosReducer from './todosReducer'
 
export default function getStore() {
    return configureStore(
        {
            reducer: todosReducer
        }
    )
}

Creating AddTodo Component – AddTodo.js

This component is used to take todo text from user and add it to todos list.

Hook – useDispatch() - is used to get dispatch function, which is used to dispatch an action.

While dispatching an action, we send todo text (using text property) as payLoad of action object to reducer.

import React from 'react'
import { useDispatch } from 'react-redux';
import { useState } from 'react';

export default function AddTodo() {
    let [todoText, setTodoText] = useState('')
    let dispatch = useDispatch()

    function addTodo(e) {
        e.preventDefault();
        dispatch({ type: 'add',  text: todoText } )  // dispatch action to reducer
        setTodoText("")
    }

    return (
        <form onSubmit={addTodo}>
            Todo : <input type="text" value={todoText} required
                    onChange={(e) => setTodoText(e.target.value)} />
            <input type="submit" value="Add" />
        </form>
    )
}

Creating ListTodos Component - ListTodos.js

This component takes state from Redux store, using useSelector() hook and displays it.

It also invokes COMPLETE_TODO action when user clicks on Done button of a todo.

When it invokes COMPLETE_TODO action, it sends id of the todo as payLoad of the action object to reducer so that reducer can identify which todo to delete.

import React from 'react'
import {useDispatch, useSelector} from 'react-redux';

export default function ListTodos() {
    let todos = useSelector(state => state.todos)     // get state from store 
    let dispatch = useDispatch()

    function completeTodo(todoId) {
        dispatch({ type: 'complete',  id: todoId })   // send id as payLoad
    }

    return (
        <ul>
            {todos.map((todo) => 
               <li key={todo.id}>
                  {todo.text}
                  <button className="btn btn-link" onClick={() => completeTodo(todo.id)}>Done</button>
               </li>)}
        </ul>
    )
}

Creating Todos Component - Todos.js

This is parent component for AddTodo and ListTodos.

import ListTodos from './ListTodos';
import AddTodo from './AddTodo';

export default function Todos() {
    return (
        <>
            <h1>Todos</h1>
            <AddTodo />
            <p></p>
            <ListTodos  />
        </>
    )
}

Index.js

This is where we create the store and associate it with Todos component by enclosing it with <Provider>, where store is specified.

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
 
import Todos from './Todos'
import getStore  from './store'
import { Provider } from 'react-redux'

const store = getStore() 

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

Sample Run

Run React application and add a few todos by entering text and clicking on Add button.

If you want to delete a todo, click on Done button on the right of todo.

Keep Learning, Keep Growing

Srikanth Pragada