Diagram

Google Draw Diagram

Now That We Have A Game Plan

  • Lets do Step 1
  • Set up your project: First, create a new React project using the create-react-app command. Delete src folder, and then re-create it. Add Back in index.js, App.js, styles.css, components/Todo.js, components/TodoList.js

Index.js

import { StrictMode } from "react"
import { createRoot } from "react-dom/client"

import App from "./App"

const rootElement = document.getElementById("root")
const root = createRoot(rootElement)

root.render(
  <StrictMode>
    <h1>Basic Todo List Evelyn</h1>
    <App />
  </StrictMode>
)

components/Todo.js

export default function Todo(props){
    return(<h1>Todo</h1>)
}

components/TodoList.js

export default function TodList(props){
    return(<h1>Todo List</h1>)
}

App.js

import "./styles.css";
import { useState, useEffect } from "react"
import TodoList from "./components/TodoList"

export default function App() {
  return (
    <div className="App">
      <TodoList/>
    </div>
  )
}

Next Lets Create the Todo and TodoList Component

  • Create a Todo component: Next, create a Todo component that will render a single todo item. This component should accept a todo object as a prop and display the todo’s text and a checkbox to mark it as complete.
  • Create a TodoList component: Then, create a TodoList component that will render a list of todo items. This component should accept an array of todo objects as a prop, and loop through the array to render a Todo component for each item.
  • Use React Hooks: Next, use React Hooks to store and manipulate the data.
  • Handle user interactions: Then, add event handlers to handle user interactions. For example, you can add an onChange handler to the checkbox to mark a todo as complete.
  • Add new todos: Add an input to the TodoList component so users can add new todos. This input should update the state when the user types enter.
  • Filter todos: Add a filter option to the TodoList component so users can filter todos by completed or incompleted.
  • Add Delete Functionality to Todo

App.js

import "./styles.css"
import { useState, useEffect } from "react"
import TodoList from "./components/TodoList"

export default function App() {
  const [todos, setTodos] = useState([])

  const addTodo = (e) => {
    const newTodo = { text: e.target.value, id: Date.now(), completed: false }
    setTodos([newTodo, ...todos])
    e.target.value = ""
  }

  const completeTodo = (id, e) => {
    const todosCopy = [...todos]
    const indexOfTodo = todosCopy.findIndex((i) => i.id === id)
    todosCopy[indexOfTodo].completed = !todosCopy[indexOfTodo].completed
    setTodos([...todosCopy])
  }

  const editTodoText = (id, e) => {
    const todosCopy = [...todos]
    const indexOfTodo = todosCopy.findIndex((i) => i.id === id)
    todosCopy[indexOfTodo].text = e.target.value
    setTodos([...todosCopy])
    e.target.value = ""
  }

  const deleteTodo = (id) => {
    const todosCopy = [...todos]
    const indexOfTodo = todosCopy.findIndex((i) => i.id === id)
    todosCopy.splice(indexOfTodo, 1)
    setTodos([...todosCopy])
  };

  return (
    <div className="App">
      <TodoList
        todos={todos}
        addTodo={addTodo}
        completeTodo={completeTodo}
        editTodoText={editTodoText}
        deleteTodo={deleteTodo}
      />
    </div>
  );
}

Todo.js

import { useState } from "react"

export default function Todo({ todo, completeTodo, editTodoText, deleteTodo }) {
  const [showInput, setShowInput] = useState(false)
  return (
    <li>
      <div className="left">
        <h2
          onClick={(e) => {
            setShowInput(!showInput)
          }}
        >
          {todo.text}
        </h2>
        <input
          style={{ display: showInput ? "block" : "none" }}
          type="text"
          onKeyDown={(e) => {
            if (e.key === "Enter") {
              editTodoText(todo.id, e)
              setShowInput(false)
            }
          }}
        />
      </div>
      <label className="middle">
        Complete
        <input
          type="checkbox"
          checked={todo.completed}
          onChange={(e) => {
            completeTodo(todo.id, e)
          }}
        />
      </label>
      <button
        checked={todo.completed}
        onClick={(e) => {
          deleteTodo(todo.id)
        }}
      >
        Delete Todo
      </button>
    </li>
  )
}

TodoList.js

import Todo from "./Todo"

export default function TodoList({
  todos,
  addTodo,
  completeTodo,
  editTodoText,
  deleteTodo
}) {
  return (
    <>
      <h1>Create Todo</h1>
      <input
        type="text"
        onKeyDown={(e) => {
          e.key === "Enter" && addTodo(e)
        }}
      />
      {todos.length ? (
        <>
          <h1>Todo Items</h1>
          <ul className="todolist">
            {todos
              .filter((i) => !i.completed)
              .map((todo) => {
                return (
                  <Todo
                    key={todo.id}
                    todo={todo}
                    completeTodo={completeTodo}
                    editTodoText={editTodoText}
                    deleteTodo={deleteTodo}
                  />
                )
              })}
          </ul>
          <h1>Completed Items </h1>
          <ul className="todolist">
            {todos
              .filter((i) => i.completed)
              .map((todo) => {
                return (
                  <Todo
                    key={todo.id}
                    todo={todo}
                    completeTodo={completeTodo}
                    editTodoText={editTodoText}
                    deleteTodo={deleteTodo}
                  />
                )
              })}
          </ul>
        </>
      ) : (
        <h1>No Todos Added Yet</h1>
      )}
    </>
  )
}

Add Persistence

Add A UseEffect

Like we mentioned at the beginning of class the useEffect hook in React is a built-in hook that allows developers to execute code after a component renders. It is triggered after every render, including the first render. It is used to perform side effects such as data fetching, manually changing the DOM, and subscribing/unsubscribing from external events. The useEffect hook takes a function as an argument and is triggered after the component renders. This function should contain all of the code that needs to be executed after the component renders. The useEffect hook is a powerful tool for managing side effects in React components.

  useEffect(() => {
    const savedTodos = localStorage.getItem("todos");
    if (savedTodos && savedTodos !== "undefined" && savedTodos !== "null") {
      setTodos(JSON.parse(savedTodos));
    }
  }, []);

We are using the useEffect here to take todos that are stored in the localStorage and automatically set our state to them.

But in order for this code to actually ever fetch anything from localStorage we need to set the localStorage everytime we update the state

localStorage.setItem("todos", JSON.stringify(Place Todos Here))

Local Storage to persist simple data

LocalStorage can be used with React to store and retrieve data in the browser after the user does a reload of the browser.

To set a value in the browser’s local storage, use the setItem() method. For example, to store a value called ‘name’, use the following code:

localStorage.setItem('name', 'John Doe');

To retrieve a value from the browser’s local storage, use the getItem() method. For example, to retrieve the value stored with the key ‘name’, use the following code:

let name = localStorage.getItem('name');

To remove a value from the browser’s local storage, use the removeItem() method. For example, to remove the value stored with the key ‘name’, use the following code:

localStorage.removeItem('name');

localStorage saves all data as a string so we also must use the JSON.stringify to stringify things like objects and arrays before we store it in localStorage

Then when we want to retrieve it we must use JSON.parse to turn it back into an object or array.

import "./styles.css"
import { useState, useEffect } from "react"
import TodoList from "./components/TodoList"

export default function App() {
  const [todos, setTodos] = useState([])

  useEffect(() => {
    const savedTodos = localStorage.getItem("todos")
    if (savedTodos && savedTodos !== "undefined" && savedTodos !== "null") {
      setTodos(JSON.parse(savedTodos))
    }
  }, [])

  const addTodo = (e) => {
    const newTodo = { text: e.target.value, id: Date.now(), completed: false }
    localStorage.setItem("todos", JSON.stringify([newTodo, ...todos]))
    setTodos([newTodo, ...todos])
    e.target.value = ""
  }

  const completeTodo = (id, e) => {
    const todosCopy = [...todos]
    const indexOfTodo = todosCopy.findIndex((i) => i.id === id)
    todosCopy[indexOfTodo].completed = !todosCopy[indexOfTodo].completed
    localStorage.setItem("todos", JSON.stringify([...todosCopy]))
    setTodos([...todosCopy])
  }

  const editTodoText = (id, e) => {
    const todosCopy = [...todos]
    const indexOfTodo = todosCopy.findIndex((i) => i.id === id)
    todosCopy[indexOfTodo].text = e.target.value
    localStorage.setItem("todos", JSON.stringify([...todosCopy]))
    setTodos([...todosCopy])
    e.target.value = ""
  }

  const deleteTodo = (id) => {
    const todosCopy = [...todos]
    const indexOfTodo = todosCopy.findIndex((i) => i.id === id)
    todosCopy.splice(indexOfTodo, 1)
    localStorage.setItem(
      "todos",
      JSON.stringify([...todosCopy])
    )
    setTodos([...todosCopy])
  }

  return (
    <div className="App">
      <TodoList
        todos={todos}
        addTodo={addTodo}
        completeTodo={completeTodo}
        editTodoText={editTodoText}
        deleteTodo={deleteTodo}
      />
    </div>
  );
}

Next lets add some styling

* {
  box-sizing: border-box;
  outline: none;
  font-size: calc(1vw + 6px);
}

body {
  background-color: rgba(240, 128, 128, 0.6);
  margin: 0;
  padding: 0;
}

#root {
  color: rgba(45, 25, 45, 0.75);
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  width: 100vw;
  margin: 0;
  padding: 0;
  min-height: 110vh;
}

#root > h1 {
  font-size: 5vw;
  text-align: center;
  text-decoration: none;
  color: white;
}

.App {
  font-family: sans-serif;
  text-align: center;
  background-color: white;
  width: 90vw;
  box-shadow: 0 4px 10px 0 rgba(0, 0, 0, 0.425);
  border-radius: 0.25rem;
  padding: 1rem;
  margin: 0 0 2rem;
}

.App > input {
  width: 40%;
}

.todolist {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  list-style: none;
  max-width: calc(90vw - 4rem);
}

.todolist > li {
  border: 0.1px solid black;
  box-shadow: 4px 4px 8px rgba(0, 0, 0, 0.7);
  width: 100%;
  text-align: center;
  display: flex;
  justify-content: space-around;
  align-items: center;
  padding: 0 1rem;
  margin: 0.15rem 0;
  background-color: lightcyan;
}

.todolist > li .left {
  flex-basis: 50%;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}

.todolist > li > .left input[type="text"] {
  width: 50%;
  height: 2rem;
}
.todolist > li input[type="text"] {
  margin-bottom: 1rem;
}

.todolist > li label {
  display: flex;
  justify-content: center;
  align-items: center;
}


h1 {
  text-decoration: underline;
  font-size: calc(1vw + 12px);
}

button {
  background-color: rgb(23, 5, 58);
  color: lightcyan;
}

References