React Adding Todos

Adding ToDo Items

In this post I'm commenting on the code required to add an individual to-do to the application, including a form on the user interface as well as back end data.

· The creation of TodoForm.js provides the form and action to add the to-do to the app. A relatively simple form with the only field in it containing an onChange event that sets the submitted value into the state.
· The form's handleSubmit function dispatches the appropriate type ("ADD_TODO") to the reducer.js file.
· reducer.js looks if the form was submitted blank or if a todo with the same text already exists - in either case the state is returned.
· If the new todo passes validation (above) then a new id is created and the text and completed statuses are set.
· Finally the new todo is added to the state and returned to the UI.

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


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