DEV Community

Mitesh Panchal
Mitesh Panchal

Posted on

React Todo App (Desktop-Only Version) - Design (Frontend Mentor)

✨ What I Built

A clean and functional Todo App using React.js - mainly built to practice component structure, state management, and dark/light theming.

This version is desktop-only for now, but I plan to make it fully responsive later.

⚙️ Features

✅ Add new tasks
❌ Delete tasks
🕹️ Toggle task completion
🌗 Dark/Light mode toggle
🔍 Filter tasks by:

🔹All
🔷Active
✅Completed

🧹 Clear all completed tasks
🔄 Real-time updates using useState and props

🔧 Tech Stack

React.js (Functional Components + Hooks)
CSS Modules for scoped styling
No external UI libraries
Modular file structure

🎯 What I Practiced

Lifting and managing state between components
Controlled form elements
Dynamic filtering
Class toggling for theme switch
Code reusability (e.g., component)

🖼️ Screenshots

  • Main.jsx
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.jsx'

createRoot(document.getElementById('root')).render(
  <StrictMode>
    <App />
  </StrictMode>,
)
Enter fullscreen mode Exit fullscreen mode
  • App.jsx
import "./App.css";
import { useState } from "react";
import TodoHeader from "./components/TodoHeader";
import TodoForm from "./components/TodoForm";
import TodoList from "./components/TodoList";

function App() {
    const [isDarkMode, setIsDarkMode] = useState(false);
    const [todoItems, setTodoItems] = useState([]);

    const handleAddTodoItems = (item) => {
        setTodoItems((items) => [...items, item]);
    };

    const handleDeleteItems = (id) => {
        setTodoItems((items) => items.filter((item) => item.id !== id));
    };

    const handleToggleComplete = (id) => {
        setTodoItems((items) =>
            items.map((item) => (item.id === id ? { ...item, completed: !item.completed } : item))
        );
    };

    const handleClearCompleted = () => {
        setTodoItems((items) => items.filter((item) => !item.completed));
    };

    return (
        <>
            <main className={`todo-app ${isDarkMode ? "dark" : "light"}`}>
                <section className="todo-app-section">
                    <TodoHeader onChangeTheme={setIsDarkMode} isDarkMode={isDarkMode} />
                    <TodoForm onAddTodoItems={handleAddTodoItems} />

                    {todoItems.length > 0 ? (
                        <TodoList
                            todoItems={todoItems}
                            setTodoItems={setTodoItems}
                            onTaskComplete={handleToggleComplete}
                            onDeleteItem={handleDeleteItems}
                            onClearCompleted={handleClearCompleted}
                        />
                    ) : (
                        ""
                    )}
                </section>
            </main>
        </>
    );
}

export default App;
Enter fullscreen mode Exit fullscreen mode
  • TodoHeader.jsx
import "../styles/Header.css";
import Button from "./ui/Button";

function TodoHeader({ isDarkMode, onChangeTheme }) {
    const handleThemeToggler = () => {
        onChangeTheme((isDarkMode) => !isDarkMode);
    };

    return (
        <header className="todo-header">
            <h1>Todo</h1>

            <Button className={"theme-toggle"} onClick={handleThemeToggler}>
                <img src={`icon-${isDarkMode ? "sun.svg" : "moon.svg"}`} alt="icon" />
            </Button>
        </header>
    );
}

export default TodoHeader;
Enter fullscreen mode Exit fullscreen mode
  • UI Button.jsx
function Button({ className, onClick, children, id }) {
    return (
        <button className={className} onClick={onClick} id={id}>
            {children}
        </button>
    );
}

export default Button;
Enter fullscreen mode Exit fullscreen mode
  • TodoForm.jsx
import { useState } from "react";
import "../styles/TodoForm.css";
import Button from "./ui/Button";

function TodoForm({ onAddTodoItems }) {
    const [task, setTask] = useState("");

    const handleForm = (e) => {
        e.preventDefault();

        if (!task.trim()) return;

        const newTask = {
            id: Date.now(),
            task: task,
            completed: false,
        };

        onAddTodoItems(newTask);
        setTask("");
    };

    return (
        <div className="todo-form-wrapper">
            <form action="" className="todo-form" onSubmit={handleForm}>
                <Button className={"todo-submit"}></Button>
                <label htmlFor="todo-input" className="todo-label"></label>
                <input
                    type="text"
                    name="todo-input"
                    id="todo-input"
                    placeholder="Create a new todo..."
                    onChange={(e) => setTask(e.target.value)}
                    value={task}
                />
            </form>
        </div>
    );
}

export default TodoForm;
Enter fullscreen mode Exit fullscreen mode
  • TodoList.jsx
import TodoItem from "./TodoItem";
import "../styles/TodoList.css";
import TodoFooter from "./TodoFooter";
import { useState } from "react";

function TodoList({ todoItems, setTodoItems, onTaskComplete, onDeleteItem, onClearCompleted }) {
    const [activeTab, setActiveTab] = useState("all");

    let filterItems;

    if (activeTab === "all") filterItems = todoItems;
    else if (activeTab === "active")
        filterItems = todoItems.filter((item) => item.completed === false);
    else filterItems = todoItems.filter((item) => item.completed === true);

    return (
        <>
            <ul className="todo-items">
                {filterItems.map((item) => {
                    return (
                        <TodoItem
                            item={item}
                            key={item.id}
                            setTodoItems={setTodoItems}
                            onTaskComplete={onTaskComplete}
                            onDeleteItem={onDeleteItem}
                        />
                    );
                })}
            </ul>
            <TodoFooter
                filter={activeTab}
                onSetFilter={setActiveTab}
                todoItems={todoItems}
                onClearCompleted={onClearCompleted}
            />
        </>
    );
}
export default TodoList;
Enter fullscreen mode Exit fullscreen mode
  • TodoItem.jsx
import Button from "./ui/Button";

function TodoItem({ item, onTaskComplete, onDeleteItem }) {
    return (
        <li className="todo-item">
            <input
                type="checkbox"
                name="todo-checkbox"
                id={`todo-checkbox-${item.id}`}
                className="todo-checkbox"
                checked={item.completed}
                onChange={() => onTaskComplete(item.id)}
            />
            <label htmlFor={`todo-checkbox-${item.id}`} className="todo-checkbox-wrapper">
                <img src="icon-check.svg" alt="check" />
            </label>

            <span className="todo-text">{item.task}</span>

            <Button className="delete-item" onClick={() => onDeleteItem(item.id)}>
                <img src="icon-cross.svg" alt="x" />
            </Button>
        </li>
    );
}

export default TodoItem;
Enter fullscreen mode Exit fullscreen mode
  • TodoFooter.jsx
import Button from "./ui/Button";
import "../styles/TodoFooter.css";

const ctaBtnText = ["All", "Active", "Completed"];

function TodoFooter({ filter, onSetFilter, todoItems, onClearCompleted }) {
    const activeTodoItems = todoItems.filter((item) => !item.completed).length;

    return (
        <div className="todo-footer">
            <p className="todo-item-count">{activeTodoItems} items left</p>

            <div className="todo-footer-cta">
                {ctaBtnText.map((btn) => (
                    <Button
                        className={`cta-action-btn`}
                        key={btn}
                        onClick={() => onSetFilter(btn.toLocaleLowerCase())}
                    >
                        <span
                            className={`${
                                filter === btn.toLowerCase() ? "cta-action-btn--active" : ""
                            }`}
                        >
                            {btn}
                        </span>
                    </Button>
                ))}
            </div>

            <Button className={"cta-action-btn"} onClick={onClearCompleted}>
                <span>Clear Completed</span>
            </Button>
        </div>
    );
}

export default TodoFooter;
Enter fullscreen mode Exit fullscreen mode

Let me know what you think or if you have any feedback! Cheers! 🚀

Top comments (0)