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.

O bien también puedes visualizar los videos siendo miembro en Youtube. Accede a miembros del canal aquí y accede a los videos de esta sección.

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