React Updating Todos

Updating ToDo Items

In this post I'm commenting on the code required to update an individual to-do to the application.
· contex.js requires the addition of an empty object to the data set called "currentTodo", to carry the updated values.
· TodoList.js requires adding an onClick event that dispatches the type "SET_CURRENT_TODO" with the payload of the currently selected to-do.
· reducer.js simply copies the state and sets the currentTodo to the submitted action.payload, hence "updating" the submitted to-do's text.
· TodoForm.js checks the currentTodo's state; if (currentTodo.text) exists then put it into the form input (setTodo(currentTodo.text)).
· The handleSubmit() function in TodoForm.js gets modified to accomodate the condion 'if currentTodo.text then dispatch UPDATE_TODO; else dispatch ADD_TODO'.
· reducer.js gets the case "UPDATE_TODO" with some error checking, then the current to-do's id gets identified, and then the entire todos string gets parsed together with several different slice functions putting everything back together again.

TodoForm.js

import React, { useState, useEffect, useContext } from 'react'
import TodosContext from '../context';

const TodoForm = () => {
    const [todo, setTodo] = useState("");
    const {state: {currentTodo={}}, dispatch} = useContext(TodosContext); 

    useEffect(() => {
        if (currentTodo.text){
            setTodo(currentTodo.text)
        } else {
            setTodo("");
        }
    }, [currentTodo.id])

    const handleSubmit = event => {
        event.preventDefault();
        if (currentTodo.text){
            dispatch({type: "UPDATE_TODO", payload: todo})
        } else {
            dispatch({type:"ADD_TODO", payload:todo});
        }
        setTodo("");
    }
    return (
        <form onSubmit={handleSubmit} className="flex justify-center p5">
            <input type="text" className=" border-black border-solid border-2" onChange={event => setTodo(event.target.value)} value={todo}/>
        </form>
    )
}

export default TodoForm


TodoList.js

import React, { useContext } from "react"; //in order to consume context need to import useContext from React, don't forget!
import TodosContext from "../context"; //the actual data that will be consumed

export default function TodoList() {
  const { state, dispatch } = useContext(TodosContext); //destructure from the context
  const title =
    state.todos.length > 0 ? `${state.todos.length} Todos` : "Nothing to do!";

  return (
    <div className="container mx-auto max-w-md text-center font-mono">
      <h1 className="text-bold">{title}</h1>
      <ul className="list-reset text-white p-0">
        {state.todos.map(todo => (
          <li
            key={todo.id}
            className="flex items-center bg-orange-dark border-black border-dashed border-2 my-2 py-4"
            style={{ backgroundColor: "orange" }}
          >
            <span
              className={`flex-1 ml-12 text-black cursor-pointer ${todo.complete &&
                "line-through text-gray-darkest"}`}
              onDoubleClick={() =>
                dispatch({ type: "TOGGLE_TODO", payload: todo })
              }
            >
              {todo.text}
            </span>
            <button
              onClick={() =>
                dispatch({ type: "SET_CURRENT_TODO", payload: todo })
              }
            >
              {" "}
              <img
                src="https://icon.now.sh/edit/0050c5"
                alt="edit icon"
                className="h-6"
              />
            </button>
            <button>
              {" "}
              <img
                src="https://icon.now.sh/delete/8b0000"
                alt="delete icon"
                className="h-6"
                onClick={() => dispatch({ type: "REMOVE_TODO", payload: todo })}
              />
            </button>
          </li>
        ))}
      </ul>
    </div>
  );
}


context.js

import React from 'react';

const TodosContext = React.createContext({
    todos: [
        {id:1, text:'Eat breakfast', complete: false},
        {id:2, text:'Do laundry', complete: false},
        {id:3, text:'Finish project', complete: true},
    ],
    currentTodo: {}
})

export default TodosContext;


reducer.js

import uuidv4 from 'uuid/v4'

export default function reducer(state, action){
    switch(action.type){
        case "ADD_TODO" :
            if(!action.payload){
                return state;
            } 
            if(state.todos.findIndex(t => t.text === action.payload) > -1) {
                return state;
            }
            const newTodo = {
                id: uuidv4(),
                text: action.payload,
                completed: false
            }
            const addedTodos = [...state.todos, newTodo]
            return {
                ...state,
                todos: addedTodos
            }

        case "SET_CURRENT_TODO" : 
            return {
                ...state,
                currentTodo: action.payload
            }    

        case "TOGGLE_TODO" : 
            const toggledTodos = state.todos.map(t => 
                t.id === action.payload.id ? {...action.payload, complete: !action.payload.complete} : t)
                return {
                    ...state, 
                    todos: toggledTodos
                }
        case "UPDATE_TODO": 
            if(!action.payload){
                return state;
            } 
            if(state.todos.findIndex(t => t.text === action.payload) > -1) {
                return state;
            }
            const updatedTodo = { ...state.currentTodo, text: action.payload }
            const updatedTodoIndex = state.todos.findIndex(
                t => t.id === state.currentTodo.id
            )
            const updatedTodos = [
                ...state.todos.slice(0, updatedTodoIndex),
                updatedTodo,
                ...state.todos.slice(updatedTodoIndex + 1)
            ]
            return {
                ...state,
                currentTodo: {},
                todos: updatedTodos
            }        
        case "REMOVE_TODO" : 
            const filteredTodos = state.todos.filter(t => t.id !== action.payload.id);
            const isRemovedTodo = state.currentTodo.id === action.payload.id ? {} : state.currentTodo;
            return {
                ...state,
                currentTodo: isRemovedTodo,
                todos: filteredTodos
            }       
        default: 
            return state;
    }
}


Link your website to this page! Copy and paste the URL below:
http://www.cfsnap.com/react/react-updating-todos/