React
Mini Project – Todo App
Build a complete todo application combining state management, forms, conditional rendering, and list operations to create a fully functional task manager.
Todo apps teach you everything. State changes. Form handling. List operations. Conditional rendering. They're simple enough to grasp but complex enough to challenge you. The DataFlow team wants to add a quick task manager to help users track their analytics goals. Before building it into the main dashboard, they need a standalone prototype to test the core features.Planning Our Todo App
Every good project starts with planning. What features do we need? What data will we track? How will users interact with it?Core Features
Add tasks, mark complete, delete tasks, filter by status
Data Structure
Each task has id, text, completed status, creation date
User Actions
Type new task, press Enter, click checkboxes, click delete
State Updates
Add to array, update by id, remove by id, toggle status
Building the Todo Structure
Start with the basic component structure. We need state for our tasks list and the input field.// TodoApp.js - Main component structure
import { useState } from 'react';
function TodoApp() {
const [tasks, setTasks] = useState([]);
const [newTask, setNewTask] = useState('');
return (
<div className="todo-app">
<h1>DataFlow Task Manager</h1>
<div className="todo-input">
<input
type="text"
placeholder="Add a new task..."
value={newTask}
onChange={(e) => setNewTask(e.target.value)}
/>
</div>
</div>
);
}What just happened?
We created two state variables. The tasks array holds our todo items, and newTask tracks what the user types. The input is controlled - its value comes from state.
Adding Tasks with Form Submission
Now we need to handle form submission. When users press Enter or click a button, we should add their task to the list.// Adding the addTask function and form handling
function TodoApp() {
const [tasks, setTasks] = useState([]);
const [newTask, setNewTask] = useState('');
const addTask = (e) => {
e.preventDefault();
if (newTask.trim() === '') return;
const task = {
id: Date.now(),
text: newTask.trim(),
completed: false
};
setTasks([...tasks, task]);
setNewTask('');
};
return (
<div className="todo-app">
<h1>DataFlow Task Manager</h1>
<form onSubmit={addTask}>
<input
type="text"
placeholder="Add a new task..."
value={newTask}
onChange={(e) => setNewTask(e.target.value)}
/>
</form>
</div>
);
}What just happened?
The form prevents default submission with e.preventDefault(). We check for empty input, create a task object with unique ID, add it to the array using spread operator, then clear the input. Try typing and pressing Enter!
Displaying the Task List
Time to show our tasks. We need to render each task with a checkbox for completion and a delete button.// Adding task list rendering
function TodoApp() {
// ... previous state and addTask function
const toggleTask = (id) => {
setTasks(tasks.map(task =>
task.id === id ? { ...task, completed: !task.completed } : task
));
};
const deleteTask = (id) => {
setTasks(tasks.filter(task => task.id !== id));
};
return (
<div className="todo-app">
<h1>DataFlow Task Manager</h1>
<form onSubmit={addTask}>
<input
type="text"
placeholder="Add a new task..."
value={newTask}
onChange={(e) => setNewTask(e.target.value)}
/>
</form>
<div className="task-list">
{tasks.map(task => (
<div key={task.id} className="task-item">
<input
type="checkbox"
checked={task.completed}
onChange={() => toggleTask(task.id)}
/>
<span className={task.completed ? 'completed' : ''}>
{task.text}
</span>
<button onClick={() => deleteTask(task.id)}>
Delete
</button>
</div>
))}
</div>
</div>
);
}What just happened?
We mapped over the tasks array to render each item. The toggleTask function uses map to update only the matching task, while deleteTask uses filter to remove it. Try checking tasks and deleting them!
Adding Filters and Status
The final step is adding filters so users can view all tasks, only active ones, or only completed ones.// Complete TodoApp with filtering
function TodoApp() {
const [tasks, setTasks] = useState([]);
const [newTask, setNewTask] = useState('');
const [filter, setFilter] = useState('all'); // all, active, completed
// ... previous functions (addTask, toggleTask, deleteTask)
const filteredTasks = tasks.filter(task => {
if (filter === 'active') return !task.completed;
if (filter === 'completed') return task.completed;
return true; // 'all'
});
const activeTasks = tasks.filter(task => !task.completed).length;
return (
<div className="todo-app">
<h1>DataFlow Task Manager</h1>
<form onSubmit={addTask}>
<input
type="text"
placeholder="Add a new task..."
value={newTask}
onChange={(e) => setNewTask(e.target.value)}
/>
</form>
<div className="filters">
<button
onClick={() => setFilter('all')}
className={filter === 'all' ? 'active' : ''}
>
All ({tasks.length})
</button>
<button
onClick={() => setFilter('active')}
className={filter === 'active' ? 'active' : ''}
>
Active ({activeTasks})
</button>
<button
onClick={() => setFilter('completed')}
className={filter === 'completed' ? 'active' : ''}
>
Completed ({tasks.length - activeTasks})
</button>
</div>
<div className="task-list">
{filteredTasks.map(task => (
<div key={task.id} className="task-item">
<input
type="checkbox"
checked={task.completed}
onChange={() => toggleTask(task.id)}
/>
<span className={task.completed ? 'completed' : ''}>
{task.text}
</span>
<button onClick={() => deleteTask(task.id)}>×</button>
</div>
))}
</div>
</div>
);
}What just happened?
We added a filter state and three filter buttons. The filteredTasks computed value filters the tasks based on the current filter. We also count active tasks to show in the button labels. Try switching filters!
Key React Patterns Used
This todo app demonstrates several essential React patterns you'll use in every project.State Management Patterns
Multiple useState hooks for different concerns. Array updates using spread operator and functional methods like map and filter. Immutable updates preserve React's ability to detect changes.
Form Handling
Controlled inputs where React state determines the input value. Form submission handling with preventDefault. Input validation to prevent empty tasks.
List Rendering
Using map to render arrays as JSX. Unique key props for each item. Event handlers with parameters using arrow functions.
Next Steps and Enhancements
Your todo app works, but there's always room for improvement. Real applications need more features and better user experience. Consider adding local storage persistence so tasks survive page refreshes. Add drag and drop reordering. Include due dates and priority levels. Add task categories or tags. Performance wise, you might optimize withuseCallback and useMemo for larger task lists. Extract components like TaskItem and FilterBar for better organization.
The patterns you learned here - state management, form handling, list operations, conditional rendering - form the foundation of most React applications. Whether you're building a simple todo app or a complex dashboard like DataFlow, these concepts remain the same.
Quiz
1. What is the correct way to delete a task with a specific ID from the tasks array in React?
2. Why do we call e.preventDefault() in the form submit handler?
3. What is the React way to toggle a task's completed status in an array?
Up Next: Mini Project – Dashboard
Build a complete analytics dashboard combining multiple components, data visualization, and interactive features to create a professional business interface.