ReviseAlgo Logo

Projects

Todo

Master building a React Todo application, covering form input state controls, filter views, localStorage persistence, and list edits.

Last Updated: July 15, 2026 12 min read

1. Learning Objectives

In this project, you will build a feature-rich Todo List application. By the end of this project, you will be able to:

  • Manage complex list structures inside React component states.
  • Create controlled form inputs for text addition and inline editing.
  • Implement filtering logic to filter lists by status (All, Active, Completed).
  • Persist data locally using the browser's localStorage API.
  • Perform inline item edits and manage editing states dynamically.

2. Overview

A **Todo List** is the classic introduction to React state management. It covers state declarations, input fields, parent-child event communication, list mapping, and browser storage persistence. In this guide, we will design a robust version with status filters, edit triggers, and data persistence.

3. Why This Project Matters

Building a Todo List teaches you core React patterns:

  • State CRUD Operations: You learn how to Create, Read, Update, and Delete array items immutably in React state.
  • Side Effects: You learn how to synchronize component state with external APIs like localStorage.

4. Real-World Analogy

Think of building a Todo application like **managing a restaurant order wheel**:

  • The Input Form: The waiter clips a new order ticket to the wheel (adds a task to the array).
  • The List State: The complete line of order tickets on the wheel. You don't scribble on existing tickets; you slide them, replace them, or pull them off the wheel when done (immutable state updates).
  • Filters: Sliding tickets to separate pending appetizers from finished desserts (filtering lists dynamically).

5. Core Concepts

Below is a comparison of standard array manipulation and immutable React state operations:

Action Standard JS (Mutative) React State (Immutable - Recommended)
Add Item array.push(item) setTodos([...todos, item])
Delete Item array.splice(index, 1) setTodos(todos.filter(t => t.id !== targetId))
Edit Item array[index].text = newText setTodos(todos.map(t => t.id === targetId ? { ...t, text: newText } : t))

6. Syntax & API Reference

This example shows how to load and persist items in browser storage:

7. Visual Diagram

This diagram displays how state changes flow through the Todo list application:

8. Live Example — Full Working Code

Below is a complete HTML page demonstrating a Todo app with localStorage persistence and filters:

What just happened? Adding tasks or checking checkboxes triggers state updates inside todos. The useEffect Hook automatically serializes and persists these updates to localStorage. Status filters are calculated dynamically during rendering without requiring separate state values.

9. Interactive Playground

Try It Yourself Challenges:

  1. Open the browser's developer tools, go to the Application tab (Storage), and watch the sandbox-todos key update in real time as you add or toggle tasks.
  2. Add a "Clear Completed" button that removes all checked items from the list.

10. Common Mistakes

Mistake Why it happens Wrong Correct
Mutating the state array directly Using mutative methods (like push, splice, or modifying properties directly) modifies the state object in place. React skips rendering updates because the array reference has not changed. todos.push(item);
setTodos(todos);
setTodos([...todos, item]);
Creating unnecessary state variables for filters Storing a separate filtered list in state (e.g. filteredTodos) makes it easy for states to get out of sync. Derive the filtered list dynamically during rendering instead. Storing both todos and filteredTodos in separate states. Store only the raw todos array and active filter string, then calculate the filtered list dynamically during rendering.

11. Best Practices

  • Keep state immutable: Always use non-mutating methods (like filter, map, and the spread operator) to update state arrays and objects.
  • Derive state values: Do not store values in state if they can be calculated from existing state variables (like filtered lists or task counts).
  • Initialize state lazily: Use callback functions to initialize state from localStorage to prevent reading from disk on every render.
  • Add unique keys: Always provide unique `key` props (e.g., using item IDs) when rendering arrays in JSX.

12. Browser Compatibility/Requirements

This project requires browser support for localStorage (supported in all modern browsers).

13. Interview Questions

Q1: How do you add, edit, and delete items from a state array in React without mutating it?

Answer: Use non-mutating JavaScript array methods:

  • Add: Create a new array using the spread operator: `setTodos(prev => [...prev, newItem])`.
  • Delete: Filter out the item to be removed: `setTodos(prev => prev.filter(item => item.id !== targetId))`.
  • Edit: Map over the array and return a new object for the updated item: `setTodos(prev => prev.map(item => item.id === targetId ? { ...item, text: newText } : item))`.

Q2: Why should we use a callback function to initialize state from `localStorage`?

Answer: Passing a callback function to `useState` (e.g. `useState(() => getStoredItems())`) enables **lazy state initialization**. This runs the initialization function only once when the component mounts. If you pass the function call directly (e.g., `useState(localStorage.getItem('key'))`), the browser will read from disk on every render, which can slow down performance.

14. Debugging Exercise

Identify why checking a checkbox in this list does not trigger a re-render in the browser:

View Solution

Diagnosis: The toggleDone function mutates the state array in place (todos[index].done = ...) and calls setTodos(todos) with the same array reference. Since React uses reference checks to identify changes, it assumes the state hasn't changed and skips re-rendering the component.

Fix: Map over the array to return a new array with a shallow copy of the updated object:

15. Practice Exercises

Exercise 1: Add dynamic task editing

Extend the Todo application to support double-clicking a task item to open an inline text input, allowing users to edit the task text and press Enter to save.

16. Scenario-Based Challenge

The Shared List Synchronization challenge:

You are designing a collaborative task list where multiple users add and complete tasks. The list must periodically fetch updates from a server API while preserving local unsaved tasks. Describe how you would merge local and remote states without losing edits or duplicating IDs.

17. Quick Quiz

Q1: Which method should be used to delete an item from an array in React state?

A) splice()

B) filter()

C) pop()

Answer: B — filter() returns a new array containing only the elements that pass the test, preserving state immutability.

18. Summary & Key Takeaways

  • Always update array states immutably in React (using filter, map, and the spread operator).
  • Derive filtered lists dynamically during rendering instead of storing them in separate state variables.
  • Use lazy state initialization to prevent reading from localStorage on every render.
  • Ensure all elements rendered in a list have unique key props to prevent rendering bugs.

19. Cheat Sheet

State Mutation Operation Syntax Example
setTodos([...todos, item]) Creates a new array to append a new item at the end.
setTodos(todos.filter(t => t.id !== id)) Filters out the item with the matching ID to delete it.
setTodos(todos.map(t => t.id === id ? newT : t)) Maps over the array and updates items with matching IDs.
---