DEV Community

Cover image for ✨ React To-Do App: A Beginner’s Thinking Guide to Building from Scratch
Srushti Patil
Srushti Patil

Posted on

✨ React To-Do App: A Beginner’s Thinking Guide to Building from Scratch

“How do I even start building something in React?”

If you've asked yourself that question, you’re not alone. React can feel intimidating at first, but when you break it down one thought at a time, it becomes a superpower.

Let’s walk through building a To-Do App — not just the code, but the thinking process behind every line. This blog isn’t about copy-pasting; it’s about helping you develop a builder’s mindset.


🧠 Step 1: Think in Components

"What am I building?"

A To-Do App is a perfect starter project because it helps you practice:

  • Displaying a list
  • Adding new items
  • Updating item state (like marking it done)
  • Removing items

So let’s break it into components:

  • App: The heart that holds everything.
  • TodoList: Displays the list of items.
  • TodoItem: Represents one task.

⚙️ Step 2: Set Up the Project

We’re using Vite for a fast and modern React setup.

npm create vite@latest react-todo-app -- --template react
cd react-todo-app
npm install
npm run dev
Enter fullscreen mode Exit fullscreen mode

Open src/App.jsx — that’s where your app lives.


📦 Step 3: Start from the Middle — The App.jsx
When I build something new, I don’t write everything at once. I start with what I understand best — in this case, managing the list of todos.

Here’s the code (with thought bubbles):

import { useState } from 'react';
import TodoList from './components/TodoList';

function App() {
  const [todos, setTodos] = useState([]); // 🧠 The to-do list
  const [text, setText] = useState('');   // ✍️ What the user is typing

  const addTodo = (e) => {
  e.preventDefault(); // ⛔ Prevent page refresh when form is submitted
  if (text.trim()) {
    const newTodo = {
      id: Date.now(),        // Unique ID using current timestamp
      text,                  // The text the user typed
      completed: false       // New tasks are not completed by default
    };
    setTodos([...todos, newTodo]); // ✅ Add the new task to the existing list
    setText('');                   // 🔄 Clear input field
  }
};


  // Toggle completion
  const toggleComplete = (id) => {
    setTodos(todos.map(todo =>
      todo.id === id ? { ...todo, completed: !todo.completed } : todo
    ));
  };

  // Delete a task
  const deleteTodo = (id) => {
    setTodos(todos.filter(todo => todo.id !== id));
  };

  return (
    <div>
      <h1>React To-Do App</h1>

      {/* 🧠 Add a task */}
      <form onSubmit={addTodo}>
        <input
          type="text"
          value={text}
          placeholder="Add a new task"
          onChange={(e) => setText(e.target.value)}
        />
        <button type="submit">Add</button>
      </form>

      {/* 🧠 Show the list */}
      <TodoList
        todos={todos}
        onToggle={toggleComplete}
        onDelete={deleteTodo}
      />
    </div>
  );
}

export default App;

Enter fullscreen mode Exit fullscreen mode

🔍 What’s happening:

  • The form is submitted → The addTodo function is called.
  • If there's something typed (text.trim()), it creates a new todo object.
  • The new task is added to the existing todos array using the spread operator.
  • setText('') clears the input so the user can type the next task.
  • Every task has a unique ID.
  • In deleteTodo → filter() creates a new array excluding the task with the matching ID.
  • The new filtered array is set back into the state using setTodos().
  • map() goes through all tasks.
  • In toggleComplete → when it finds the one with the matching ID, it flips the value of completed.
  • The ...todo keeps everything else the same, only changing completed.

🔄 Step 4: Build TodoList & TodoItem

Why separate them?

Because every task can be reused, styled, or enhanced on its own. Keeping things modular = clean code.

TodoList.jsx

import TodoItem from './TodoItem';

function TodoList({ todos, onToggle, onDelete }) {
  return (
    <ul>
      {todos.map(todo => (
        <TodoItem
          key={todo.id}
          todo={todo}
          onToggle={onToggle}
          onDelete={onDelete}
        />
      ))}
    </ul>
  );
}

export default TodoList;

Enter fullscreen mode Exit fullscreen mode

TodoItem.jsx

function TodoItem({ todo, onToggle, onDelete }) {
  return (
    <li>
      <span
        onClick={() => onToggle(todo.id)}
        style={{
          textDecoration: todo.completed ? 'line-through' : 'none',
          cursor: 'pointer',
          marginRight: '10px'
        }}
      >
        {todo.text}
      </span>
      <button onClick={() => onDelete(todo.id)}>❌</button>
    </li>
  );
}

export default TodoItem;

Enter fullscreen mode Exit fullscreen mode

🧩 Step 5: Test the Flow (and Think Like the User)

  1. Can I add a task?
  2. Can I mark it as done?
  3. Can I delete it?
  4. Does the app feel responsive and logical?

*If yes, congrats! You’ve just built your first real React app *🎉


🌱 Thought-Driven Dev Tips

  1. Don’t start with a blank screen — start with what makes sense to you.
  2. Build piece by piece. Get one feature working before adding the next.
  3. Keep asking: "What should happen when the user clicks here?"
  4. Think in state: “What changes in the app when I do something?”

Top comments (0)