REACT Lesson 30 – Mini Project - Todo App | Dataplexa
LESSON 30

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>
  );
}
DataFlow Task Manager

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>
  );
}
DataFlow Task Manager

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>
  );
}
DataFlow Task Manager

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>
  );
}
DataFlow Task Manager

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.

But the most important pattern is thinking in React. Break complex UI into small, focused functions. Keep state as simple as possible. Update state immutably.

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 with useCallback 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.