Skip to content

React Todo APP

En esta sección realizaremos una práctica para ir aterrizando todo lo aprendido.

⭐ Videos Premium ⭐

Esta sección es parte del curso en Udemy. Si quieres acceder a ella, puedes comprar el curso en Udemy: React + Firebase by bluuweb.

Recursos

App.jsx

jsx
import { useState } from "react";
import Formulario from "./components/Controlado";
import Todos from "./components/Todos";

const initialStateTodos = [
  {
    id: 1,
    title: "Todo #01",
    description: "Descripción #01",
    state: "pendiente",
    priority: true,
  },
  {
    id: 2,
    title: "Todo #02",
    description: "Descripción #02",
    state: "completado",
    priority: false,
  },
];

const App = () => {
  const [todos, setTodos] = useState(initialStateTodos);

  const addTodo = (todo) => {
    setTodos([...todos, todo]);
  };

  return (
    <div className="container">
      <h1>Formularios</h1>
      <Formulario addTodo={addTodo} />
      <Todos todos={todos} />
    </div>
  );
};
export default App;

Todos.jsx

jsx
const Todos = ({ todos }) => {
  return (
    <main>
      <h2>Todos</h2>
      <ul>
        {todos.map((todo) => (
          <li key={todo.id}>
            <h3>{todo.title}</h3>
            <p>{todo.description}</p>
            <p>{todo.state ? "Completado" : "Pendiente"}</p>
            <p>{todo.priority ? "Prioridad" : "Sin prioridad"}</p>
          </li>
        ))}
      </ul>
    </main>
  );
};
export default Todos;

Formulario.jsx

jsx
const Controlado = ({ addTodo }) => {
    const [todo, setTodo] = useState({
        title: "Todo #01",
        description: "Descripción #01",
        state: "pendiente",
        priority: true,
    });

    const { title, description, state, priority } = todo;

    const handleSubmit = (e) => {
        e.preventDefault();
        console.log(title, description, state);
        if (!title.trim() || !description.trim()) {
            console.log("Datos incompletos");
            return;
        }
        addTodo({
            id: Date.now(),
            ...todo,
            state: state === "completado",
        });
    };
    ...
};

SweetAlert2

npm install sweetalert2
jsx
import Swal from "sweetalert2";
jsx
const handleSubmit = (e) => {
    e.preventDefault();
    console.log(title, description, state);
    if (!title.trim() || !description.trim()) {
        console.log("Datos incompletos");
        Swal.fire({
            title: "Error!",
            text: "Título y descripción son obligatorios",
            icon: "error",
        });
        return;
    }
    addTodo({
        id: Date.now(),
        ...todo,
        state: state === "completado",
    });
    Swal.fire({
        position: "center",
        icon: "success",
        title: "Tarea agregada con éxito",
        showConfirmButton: false,
        timer: 1500,
    });
};

Todos.jsx

jsx
import Todo from "./Todo";

const Todos = ({ todos }) => {
  return (
    <main>
      <h2 className="text-center my-5">Todos</h2>
      <ul className="list-group">
        {todos.map((todo) => (
          <Todo
            key={todo.id}
            todo={todo}
          />
        ))}
      </ul>
    </main>
  );
};
export default Todos;

Todo.jsx

jsx
const Todo = ({ todo }) => {
  const { title, description, state, priority } = todo;

  return (
    <li className="list-group-item d-flex justify-content-between align-items-start">
      <div>
        <h5 className={`${state && "text-decoration-line-through"}`}>
          {title}
        </h5>
        <p>{description}</p>
        <div className="d-flex gap-2">
          <button className="btn btn-sm btn-danger">Eliminar</button>
          <button className="btn btn-sm btn-info">Actualizar</button>
        </div>
      </div>
      <span className="badge bg-primary rounden-pill">
        {priority && "Prioridad"}
      </span>
    </li>
  );
};

export default Todo;

Delete Todo

App.jsx

jsx
const deleteTodo = (id) => {
  const newTodos = todos.filter((todo) => todo.id !== id);
  setTodos(newTodos);
};

return (
  <div className="container mb-5">
    <h1 className="my-5">Formularios</h1>
    <Formulario addTodo={addTodo} />
    <Todos
      todos={todos}
      deleteTodo={deleteTodo}
    />
  </div>
);

Todos.jsx

jsx
import Todo from "./Todo";

const Todos = ({ todos, deleteTodo }) => {
  return (
    <main>
      <h2 className="text-center my-5">Todos</h2>
      <ul className="list-group">
        {todos.map((todo) => (
          <Todo
            key={todo.id}
            todo={todo}
            deleteTodo={deleteTodo}
          />
        ))}
      </ul>
    </main>
  );
};
export default Todos;

Todo.jsx

jsx
const Todo = ({ todo, deleteTodo }) => {
  const { title, description, state, priority } = todo;

  return (
    <li className="list-group-item d-flex justify-content-between align-items-start">
      <div>
        <h5 className={`${state && "text-decoration-line-through"}`}>
          {title}
        </h5>
        <p>{description}</p>
        <div className="d-flex gap-2">
          <button
            className="btn btn-sm btn-danger"
            onClick={() => deleteTodo(todo.id)}
          >
            Eliminar
          </button>
          <button className="btn btn-sm btn-info">Actualizar</button>
        </div>
      </div>
      <span className="badge bg-primary rounden-pill">
        {priority && "Prioridad"}
      </span>
    </li>
  );
};

export default Todo;

Upadate Todo

jsx
const updateTodo = (id) => {
  const newTodos = todos.map((todo) => {
    if (todo.id === id) {
      todo.state = !todo.state;
    }
    return todo;
  });
  setTodos(newTodos);
};

Todo.jsx

jsx
<button
  className="btn btn-sm btn-info"
  onClick={() => updateTodo(id)}
>
  Actualizar
</button>

Order Todo

jsx
const orderTodos = (arrayTodos) => {
  //https://stackoverflow.com/questions/17387435/javascript-sort-array-of-objects-by-a-boolean-property
  return arrayTodos.sort((a, b) => {
    if (a.priority === b.priority) {
      return 0;
    }
    if (a.priority) {
      return -1;
    }
    if (!a.priority) {
      return 1;
    }
  });
};

useEffect(() => {
  setTodos(orderTodos(todos));
  console.log("useEffect");
}, [todos]);

return (
  <div className="container mb-5">
    <h1 className="my-5">Formularios</h1>
    <Formulario addTodo={addTodo} />
    <Todos
      todos={orderTodos(todos)}
      deleteTodo={deleteTodo}
      updateTodo={updateTodo}
    />
  </div>
);

LocalStorage

jsx
const initialStateTodos = JSON.parse(localStorage.getItem("todos")) || [];

const App = () => {
    const [todos, setTodos] = useState(initialStateTodos);

    // Solo utilizamos un useEffect para actualizar LocalStorage
    useEffect(() => {
        localStorage.setItem("todos", JSON.stringify(todos));
    }, [todos]);

Deploy

npm run build