CC
0 XP
0

Chapter 2: Build Core Todo

Add Todo Form

build5 min

Add Todo Form

Now we're building the "C" in CRUD -- creating new todos. Let's build a form component and wire it into our app.

The prompt

"Create an AddTodo component at src/components/AddTodo.tsx. It should have a text input and a submit button on the same line. When the user types a todo and presses Enter or clicks the button, call an onAdd callback prop with the todo title. Clear the input after submission. Don't allow empty submissions. Use Tailwind CSS for styling."

What Claude Code creates

'use client';
 
import { useState, FormEvent } from 'react';
 
interface AddTodoProps {
  onAdd: (title: string) => void;
}
 
export default function AddTodo({ onAdd }: AddTodoProps) {
  const [title, setTitle] = useState('');
 
  const handleSubmit = (e: FormEvent) => {
    e.preventDefault();
    const trimmed = title.trim();
    if (!trimmed) return;
    onAdd(trimmed);
    setTitle('');
  };
 
  return (
    <form onSubmit={handleSubmit} className="flex gap-2">
      <input
        type="text"
        value={title}
        onChange={(e) => setTitle(e.target.value)}
        placeholder="What needs to be done?"
        className="flex-1 px-4 py-2 border border-gray-300 rounded-lg
                   focus:outline-none focus:ring-2 focus:ring-blue-500
                   focus:border-transparent"
      />
      <button
        type="submit"
        className="px-6 py-2 bg-blue-500 text-white rounded-lg
                   hover:bg-blue-600 transition-colors
                   disabled:opacity-50 disabled:cursor-not-allowed"
        disabled={!title.trim()}
      >
        Add
      </button>
    </form>
  );
}
Tip

Notice the details Claude Code handles: trim() to prevent whitespace-only todos, disabled state on the button, keyboard submit with Enter, and focus ring styles for accessibility. These are the small things that add up.

Wire it into the page

"Update page.tsx to include the AddTodo component above the TodoList. When a new todo is added, create a Todo object with a generated ID, set completed to false, and add it to the beginning of the list."

'use client';
 
import { useState } from 'react';
import { Todo } from '@/types/todo';
import { sampleTodos } from '@/data/sample-todos';
import { generateId } from '@/utils/generateId';
import TodoList from '@/components/TodoList';
import AddTodo from '@/components/AddTodo';
 
export default function Home() {
  const [todos, setTodos] = useState<Todo[]>(sampleTodos);
 
  const addTodo = (title: string) => {
    const newTodo: Todo = {
      id: generateId(),
      title,
      completed: false,
      createdAt: new Date().toISOString(),
    };
    setTodos([newTodo, ...todos]);
  };
 
  return (
    <div className="space-y-4">
      <AddTodo onAdd={addTodo} />
      <TodoList todos={todos} />
    </div>
  );
}

Test it out

  1. Type "Buy groceries" in the input field
  2. Press Enter or click the Add button
  3. The new todo should appear at the top of the list
  4. The input should clear automatically
  5. Try submitting an empty input -- nothing should happen
⚠️Warning

New todos will disappear when you refresh the page. That's expected! We're using in-memory state for now. We'll add localStorage persistence in section 15.

Understanding the callback pattern

The onAdd callback is a fundamental React pattern. The form component doesn't know how todos are stored -- it just says "hey, the user wants to add this title." The parent component decides what to do with it.

"Why is the onAdd callback defined in the parent instead of having AddTodo manage the todos array directly?"

Claude Code will explain separation of concerns: the form handles user input, the page handles data management. This makes both components reusable and testable.

You've just built your first interactive feature with Claude Code. The app can now create and display todos.

Important

Milestone: Your app is interactive! Try it — type a todo and hit Enter. Watch it appear in the list. You've just built a working interactive application with Claude Code. The app now creates and displays data. From here, every new feature builds on this foundation.

Important

Try it: type a todo and press Enter. It appears in your list. Your app is now interactive — you're creating real data, not just displaying samples. This is the moment your project goes from a static page to a living application.

Your first commit with Claude Code

Now that the app is interactive, let's save your progress with a git commit — using Claude Code:

"/commit"

That's it. The /commit slash command tells Claude Code to review your changes, write a descriptive commit message, and commit. You can also be more specific:

"Commit the current changes with a message about adding the todo form with state management"

Tip

Make it a habit to commit after each working feature. You'll have a clean git history and can always roll back if something goes wrong. /commit is one of the most-used slash commands in real Claude Code workflows.