React Toggling Todos

Toggling ToDo Items

In this post I'm commenting on the code required to double-click on an individual to-do to change it's completed status in the user interface.

TodoList.js:
· When a user double-clicks the text of the todo, an action "dispatches" (TodoList line 22) to the reducer.js file in the case statement for "TOGGLE_TODO" (reducer.js line 29) . The entire todo list in the state gets sent in a payload and gets mapped state.todos.map() and based on the current todo's ID the entire todo list is copied into the payload and it's "completed" status changed to the opposite of what it was (hence the "toggle" description).
· Back in the UI the classNames are dynamically rendered on the CSS call and based on the todo's complete status the css classes for gray, line-throughs are added (TodoList.js line 20).

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>
  );
}


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-toggling-todos/