Todo App
Source: Dev.to
Introduction
After completing my first logic‑focused project (Counters), I wanted to take the next natural step in complexity — not by improving the UI, but by challenging my thinking.
This led me to Project 2: Todo App (Logic‑First, Not UI).
Why a Todo App?
Counters helped me understand single‑value state logic. A Todo app forces you to think in terms of:
- Arrays instead of single values
- CRUD operations (add, update, delete)
- Conditional rendering
- Edge cases and state consistency
This is where many React learners start to struggle — which makes it the perfect place to grow.
The goal of this project was not to build a fancy interface, but to strengthen my problem‑solving approach, write predictable state updates, and handle real‑world logic scenarios that appear in almost every frontend application.
In this article, I’ll break down how I approached the Todo app step by step, the logic decisions I made, the mistakes I encountered, and what I learned by solving them.
Level‑by‑Level Development 🚀
Level 1: Add Todo Creation with Validation
Commit: Level 1: Add todo creation with validation
import { useState } from "react";
function TodoApp() {
const [todos, setTodos] = useState([]);
const [task, setTask] = useState("");
const handleSubmit = () => {
// Basic validation – prevent empty or whitespace‑only tasks
if (task.trim() === "") {
alert("Enter task to add");
return;
}
// Immutable update – create a new array with the new task
setTodos([...todos, task]);
// Reset input for better UX
setTask("");
};
return (
setTask(e.target.value)}
className="border rounded px-2 py-2 w-80 m-2"
/>
Add Task
{/* Render the list of todos */}
{todos.map((todo, idx) => (
{todo}
))}
);
}
Learnings
- Controlled Inputs – Managed form inputs using
useState, keeping the input value fully controlled by React state. - Basic Validation – Prevented empty or whitespace‑only todos from being added, ensuring clean data.
- Immutable State Updates (Arrays) – Used the spread operator (
[...todos, task]) to create a new array instead of mutating the existing one, adhering to React’s immutability rule and guaranteeing proper re‑rendering. - Resetting Input for Better UX – Cleared the input field after adding a todo with
setTask(""). - Rendering Dynamic Lists – Leveraged
map()to render todos dynamically.