Let's think about this for a second
Part 1 only had static data and layout — in Part 2, we'll add the logic that makes the app something users can actually interact with. You'll practice typing a new task into a form, handling the submit event, and updating state immutably with setTasks. Toggling a task's done status and deleting it also need to be passed back up from TaskItem to the parent as callbacks. This part covers the core value of the project: managing user data interactively.
Let's build it for real
Add a form and an input field inside the TaskTracker component — declare another useState for it as a controlled input. In the handleSubmit function, call event.preventDefault(), then add the input value into the tasks array as {id: Date.now(), text, done: false} using setTasks([...tasks, newTask]). Pass onToggle and onDelete callback props to TaskItem, and call the parent's function from the checkbox click event and the delete button click event. In the toggle function, use .map() to flip only the done field of the task with the matching id; in the delete function, use .filter() to keep only the tasks whose id doesn't match.
Code Example
// src/components/TaskTracker.jsx
import { useState } from "react";
import TaskItem from "./TaskItem";
function TaskTracker() {
const [tasks, setTasks] = useState([
{ id: 1, text: "Learn React basics", done: true },
]);
const [text, setText] = useState("");
function handleSubmit(e) {
e.preventDefault();
if (!text.trim()) return;
const newTask = { id: Date.now(), text, done: false };
setTasks([...tasks, newTask]);
setText("");
}
function toggleTask(id) {
setTasks(
tasks.map((task) =>
task.id === id ? { ...task, done: !task.done } : task
)
);
}
function deleteTask(id) {
setTasks(tasks.filter((task) => task.id !== id));
}
return (
<div className="task-tracker">
<h2>My Tasks</h2>
<form onSubmit={handleSubmit}>
<input
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="Add a new task..."
/>
<button type="submit">Add</button>
</form>
<ul>
{tasks.map((task) => (
<TaskItem
key={task.id}
task={task}
onToggle={() => toggleTask(task.id)}
onDelete={() => deleteTask(task.id)}
/>
))}
</ul>
</div>
);
}
export default TaskTracker;
// src/components/TaskItem.jsx
function TaskItem({ task, onToggle, onDelete }) {
return (
<li className={task.done ? "task-item done" : "task-item"}>
<input type="checkbox" checked={task.done} onChange={onToggle} />
<span>{task.text}</span>
<button onClick={onDelete}>Delete</button>
</li>
);
}
export default TaskItem;Type text into the input box and hit Add — a new task instantly appears in the list. Click a checkbox and the strikethrough toggles; click Delete and the task instantly disappears from the list.Try it in 5 minutes
Try making the Add button disabled when the task text field is an empty string — finish this within 5 minutes.
A quick word of caution
Generating id with Date.now() is fine for project practice, but in a production app you should use a more precise id generator, like the uuid library.