Study

Build your first ToDo App with React and host it on GitHub Pages

View it live here

Contents


Installation & create-react-project

Useful extensions for VS Code and Google Chrome

For Visual Studio Code (VSCode keyboard shortcuts)

Delete unnecesary files after create-react-app

Create new files in src/components/...

Add these pre-made CSS styles to our app:

header, form { min-height: 15vh; display: flex; flex-wrap: wrap; justify-content: center; align-items: center; } form input, form button { padding: 0.5rem; font-size: 2rem; border: none; background: white; } form button { color: #17cbff; background: #f7fffe; cursor: pointer; transition: all 0.3s ease; } form button:hover { background: #0095bf; color: white; } .todo-container { display: flex; justify-content: center; align-items: center; }

.todo-list { min-width: 30%; list-style: none; }

.todo { margin: 0.5rem; background: white; font-size: 1.5rem; color: black; display: flex; justify-content: space-between; align-items: center; transition: all 1s ease; } .filter-todo { padding: 1rem; } .todo li { flex: 1; }

.trash-btn, .complete-btn { background: #f35252; color: white; border: none; padding: 1rem; cursor: pointer; font-size: 1rem; } .complete-btn { background: #18edfe; } .todo-item { padding: 0rem 0.5rem; }

.fa-trash, .fa-check { pointer-events: none; }

.fall { transform: translateY(10rem) rotateZ(20deg); opacity: 0; }

.completed { text-decoration: line-through; opacity: 0.5; }

/*CUSTOM SELECTOR */ select { -webkit-appearance: none; -moz-appearance: none; -ms-appearance: none; appearance: none; outline: 0; box-shadow: none; border: 0 !important; background-image: none; }

/* Custom Select / .select { margin: 1rem; position: relative; overflow: hidden; } select { color: #00394a; font-family: “Poppins”, sans-serif; cursor: pointer; width: 12rem; } / Arrow */ .select::after { content: “\25BC”; position: absolute; top: 0; right: 0; padding: 1rem; background: #17cbff; cursor: pointer; pointer-events: none; }

.select:hover::after { background: #0095bf; color: white; transition: all 0.3s ease; }

- In `/public/index.html/` add in `<head>`:
```html
    <link
      rel="stylesheet"
      href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.12.1/css/all.min.css"
      integrity="sha256-mmgLkCYLUQbXn0B1SRqzHar6dCnv9oZFPEC1g1cwlkk="
      crossorigin="anonymous"
    />

Hook TodoAddForm.js to the App.js

export default TodoAddForm;

- Hook TodoAddForm.js to the App.js. In `App.js` by importing and adding `<TodoAddForm />`:
```js
import React from "react";
import "./App.css";
import TodoAddForm from "./components/TodoAddForm";

function App() {
  return (
    <div className="App">
      <header>
        <h1>ToDo List. Yay.</h1>
      </header>
      <TodoAddForm />
    </div>
  );
}

On TodoList.js

import React from "react";

const TodoList = ({ todos, setTodos, filteredTodos }) => {
  return (
    <div className="todo-container">
      <ul className="todo-list">

      </ul>
    </div>
  );
};

export default TodoList;

Add State to App.js in order to input data

import React, { useState } from "react";

function App() {
  const [inputText, setInputText] = useState("");
  const [todos, setTodos] = useState([]);
  
  ...
  
  return (
    <div className="App">
      <header>
        <h1>ToDo List. Yay.</h1>
      </header>
      <TodoAddForm
        inputText={inputText}
        setInputText={setInputText}
        todos={todos}
        setTodos={setTodos}
      />
    </div>
- Now every time we write in our for and hit the + button, a new state will be created (see with React Chrome Extension)

## <a name="workshowtodos"></a>Show todos in browser/UI (render them) + Complete and Delete
- In `TodoAddForm.js`, add `value={inputText}`:
```js
  return (
    <form>
      <input
        value={inputText}
        onChange={inputTextHandler}
        type="text"
        className="todo-input"
      />
   };

const TodoList = ({ todos, setTodos, filteredTodos }) => { return ( <div className="todo-container"> <ul className="todo-list"> {filteredTodos.map((todo) => ( <TodoItem setTodos={setTodos} todos={todos} key={todo.id} text={todo.text} id={todo.id} todo={todo} /> ))} </ul> </div> ); };

export default TodoList;

- And create `TodoItem.js` and add:
```js
import React from "react";

const TodoItem = ({ text, todo, setTodos, todos }) => {
  const deleteHandler = () => {
    setTodos(todos.filter((el) => el.id !== todo.id));
  };
  const completeHandler = () => {
    setTodos(
      todos.map((item) => {
        if (item.id === todo.id) {
          return {
            ...item,
            completed: !item.completed,
          };
        }
        return item;
      })
    );
  };
  return (
    <div className="todo">
      <li className={`todo-item ${todo.completed ? "completed" : ""}`}>
        {text}
      </li>
      <button onClick={completeHandler} className="complete-btn">
        <i className="fas fa-check"></i>
      </button>
      <button onClick={deleteHandler} className="trash-btn">
        <i className="fas fa-trash"></i>
      </button>
    </div>
  );
};

export default TodoItem;

Filter/Show Todo items baed on select dropdown “all”/”completed”/”uncompleted”




COMPLETE CODE

TodoAddForm.js

import React from "react";

const TodoAddForm = ({
  setInputText,
  todos,
  setTodos,
  inputText,
  setStatus,
}) => {
  // JavaScript functions
  const inputTextHandler = (e) => {
    // console.log(e.target.value);
    setInputText(e.target.value);
  };
  const submitTodoHandler = (e) => {
    e.preventDefault();
    if (inputText !== "") {
      setTodos([
        ...todos,
        {
          id: Math.floor(Math.random() * 1000),
          text: inputText,
          completed: false,
        },
      ]);
    }
    setInputText("");
  };
  const statusHandler = (e) => {
    setStatus(e.target.value);
  };

  // Render HTML
  return (
    <form>
      <input
        value={inputText}
        onChange={inputTextHandler}
        type="text"
        className="todo-input"
      />
      <button onClick={submitTodoHandler} className="todo-button" type="submit">
        <i className="fas fa-plus-square"></i>
      </button>
      <div className="select">
        <select onChange={statusHandler} name="todos" className="filter-todo">
          <option value="all">All</option>
          <option value="completed">Completed</option>
          <option value="uncompleted">Uncompleted</option>
        </select>
      </div>
    </form>
  );
};

export default TodoAddForm;

TodoList.js

import React from "react";
import TodoItem from "./TodoItem";

const TodoList = ({ todos, setTodos, filteredTodos }) => {
  return (
    <div className="todo-container">
      <ul className="todo-list">
        {filteredTodos.map((todo) => (
          <TodoItem
            setTodos={setTodos}
            todos={todos}
            key={todo.id}
            text={todo.text}
            id={todo.id}
            todo={todo}
          />
        ))}
      </ul>
    </div>
  );
};

export default TodoList;

TodoItem.js

import React from "react";

const TodoItem = ({ text, todo, setTodos, todos }) => {
  const deleteHandler = () => {
    setTodos(todos.filter((el) => el.id !== todo.id));
  };
  const completeHandler = () => {
    setTodos(
      todos.map((item) => {
        if (item.id === todo.id) {
          return {
            ...item,
            completed: !item.completed,
          };
        }
        return item;
      })
    );
  };
  return (
    <div className="todo">
      <li className={`todo-item ${todo.completed ? "completed" : ""}`}>
        {text}
      </li>
      <button onClick={completeHandler} className="complete-btn">
        <i className="fas fa-check"></i>
      </button>
      <button onClick={deleteHandler} className="trash-btn">
        <i className="fas fa-trash"></i>
      </button>
    </div>
  );
};

export default TodoItem;

App.js

 import React, { useState, useEffect } from "react";
import "./App.css";
import TodoAddForm from "./components/TodoAddForm";
import TodoList from "./components/TodoList";

function App() {
  const [inputText, setInputText] = useState("");
  const [todos, setTodos] = useState([]);
  const [status, setStatus] = useState("all");
  const [filteredTodos, setFilteredTodos] = useState([]);

  const filterHandler = () => {
    switch (status) {
      case "completed":
        setFilteredTodos(todos.filter((todo) => todo.completed === true));
        break;
      case "uncompleted":
        setFilteredTodos(todos.filter((todo) => todo.completed === false));
        break;
      default:
        setFilteredTodos(todos);
        break;
    }
  };

  // Save to local (browser cache)
  const saveLocalTodos = () => {
    localStorage.setItem("todos", JSON.stringify(todos));
  };
  const getLocalTodos = () => {
    if (localStorage.getItem("todos") !== null) {
      let todoLocal = JSON.parse(localStorage.getItem("todos"));
      setTodos(todoLocal);
    }
  };

  useEffect(() => {
    getLocalTodos(); // function that runs once when the app starts
  }, []);

  useEffect(() => {
    filterHandler();
    saveLocalTodos();
  }, [todos, status]); // function that runs every time "todos" changes

  return (
    <div className="App">
      <header>
        <h1>ToDo List. Yay.</h1>
      </header>
      <TodoAddForm
        inputText={inputText}
        todos={todos}
        setTodos={setTodos}
        setInputText={setInputText}
        setStatus={setStatus}
      />
      <TodoList
        setTodos={setTodos}
        todos={todos}
        filteredTodos={filteredTodos}
      />
    </div>
  );
}

export default App;

Build your React App and deploy it on GitHub Pages

Credits: