← Back to tutorials

Build a Todo List App

Create a fully functional todo list application with add, delete, and toggle features.

Beginner⏱️ 2 hours

Overview

In this hands-on tutorial, you'll build a complete Todo List application from scratch. This classic project teaches fundamental React concepts through practical implementation. You'll create a beautiful, responsive interface where users can add tasks, mark them as complete, and delete them. Along the way, you'll master state management, event handling, and component structure - essential skills for any React developer.

🎯 What You'll Learn

  • Master React state management using the useState hook
  • Handle user inputs and form submissions in React
  • Implement CRUD operations (Create, Read, Update, Delete)
  • Apply conditional rendering and dynamic CSS classes
  • Structure components for maintainability and reusability
  • Work with arrays and objects in React state
  • Handle keyboard events for better user experience

🚀 Project Features

Add new todos with a clean input interface
Mark todos as complete/incomplete with checkboxes
Delete individual todos with confirmation
Real-time UI updates as state changes
Keyboard support (Enter key to add todos)
Visual feedback for completed items
Responsive design that works on all devices
Clean, modern UI with smooth animations

🛠️ Technologies & Tools

React 18+ with functional componentsReact Hooks (useState)CSS3 for styling and animationsJavaScript ES6+ featuresCreate React App for project setup

⏱️ Time Breakdown

Project Setup15 minutes
Component Structure20 minutes
State Management30 minutes
Event Handlers25 minutes
Styling & Polish30 minutes

📊 Difficulty Level: Beginner

This tutorial is perfect for beginners who have just learned React basics. We start with simple concepts and gradually build complexity. Each step is thoroughly explained with clear code examples. No advanced patterns are used - just pure React fundamentals that form the foundation for more complex applications.

💼 Real-World Applications

The skills you learn in this tutorial are used in:

  • Task management systems in project management tools
  • Shopping lists in e-commerce applications
  • Checklist features in productivity apps
  • Assignment trackers in educational platforms
  • Daily routine managers in wellness apps

Prerequisites:

  • Basic React knowledge
  • Understanding of useState hook

Step 1: Project Setup

Let's start by creating a new React application and setting up our project structure.

1npx create-react-app todo-list-app
2cd todo-list-app
3npm start
4
5// Create a new file: src/components/TodoList.js

💡 We'll build our todo list as a separate component to keep our code organized.

Step 2: Create the TodoList Component

First, let's create the basic structure of our TodoList component with state management.

1import React, { useState } from 'react';
2
3function TodoList() {
4 const [todos, setTodos] = useState([]);
5 const [inputValue, setInputValue] = useState('');
6
7 return (
8 <div className="todo-list">
9 <h1>My Todo List</h1>
10 <div className="input-group">
11 <input
12 type="text"
13 value={inputValue}
14 onChange={(e) => setInputValue(e.target.value)}
15 placeholder="Add a new todo..."
16 />
17 <button>Add</button>
18 </div>
19 <ul>
20 {todos.map((todo) => (
21 <li key={todo.id}>{todo.text}</li>
22 ))}
23 </ul>
24 </div>
25 );
26}
27
28export default TodoList;

💡 We use useState to manage our todos array and the input field value. Each todo will be an object with id, text, and completed status.

Step 3: Implement Add Todo Functionality

Now let's add the ability to create new todos when the user clicks the Add button or presses Enter.

1const addTodo = () => {
2 if (inputValue.trim() !== '') {
3 const newTodo = {
4 id: Date.now(),
5 text: inputValue,
6 completed: false
7 };
8 setTodos([...todos, newTodo]);
9 setInputValue('');
10 }
11};
12
13const handleKeyPress = (e) => {
14 if (e.key === 'Enter') {
15 addTodo();
16 }
17};
18
19// Update the input and button:
20<input
21 type="text"
22 value={inputValue}
23 onChange={(e) => setInputValue(e.target.value)}
24 onKeyPress={handleKeyPress}
25 placeholder="Add a new todo..."
26/>
27<button onClick={addTodo}>Add</button>

💡 We create a unique ID using Date.now() and add the new todo to our state array using the spread operator.

Step 4: Add Toggle and Delete Functions

Let's add the ability to mark todos as complete and delete them.

1const toggleTodo = (id) => {
2 setTodos(todos.map(todo =>
3 todo.id === id ? { ...todo, completed: !todo.completed } : todo
4 ));
5};
6
7const deleteTodo = (id) => {
8 setTodos(todos.filter(todo => todo.id !== id));
9};
10
11// Update the todo items:
12<ul>
13 {todos.map((todo) => (
14 <li key={todo.id} className={todo.completed ? 'completed' : ''}>
15 <input
16 type="checkbox"
17 checked={todo.completed}
18 onChange={() => toggleTodo(todo.id)}
19 />
20 <span>{todo.text}</span>
21 <button onClick={() => deleteTodo(todo.id)}>Delete</button>
22 </li>
23 ))}
24</ul>

💡 We use map to update the completed status and filter to remove todos from the array.

Step 5: Add Styling

Finally, let's add some CSS to make our todo list look great.

1/* Add to src/components/TodoList.css */
2.todo-list {
3 max-width: 500px;
4 margin: 50px auto;
5 padding: 20px;
6 background: white;
7 border-radius: 10px;
8 box-shadow: 0 2px 10px rgba(0,0,0,0.1);
9}
10
11.input-group {
12 display: flex;
13 margin-bottom: 20px;
14}
15
16.input-group input {
17 flex: 1;
18 padding: 10px;
19 border: 2px solid #ddd;
20 border-radius: 5px 0 0 5px;
21 font-size: 16px;
22}
23
24.input-group button {
25 padding: 10px 20px;
26 background: #4CAF50;
27 color: white;
28 border: none;
29 border-radius: 0 5px 5px 0;
30 cursor: pointer;
31}
32
33.todo-list ul {
34 list-style: none;
35 padding: 0;
36}
37
38.todo-list li {
39 display: flex;
40 align-items: center;
41 padding: 10px;
42 border-bottom: 1px solid #eee;
43}
44
45.todo-list li.completed span {
46 text-decoration: line-through;
47 opacity: 0.6;
48}
49
50.todo-list li button {
51 margin-left: auto;
52 background: #f44336;
53 color: white;
54 border: none;
55 padding: 5px 10px;
56 border-radius: 3px;
57 cursor: pointer;
58}

💡 Import this CSS file in your TodoList component to apply the styles.

Complete Code

1import React, { useState } from 'react';
2import './TodoList.css';
3
4function TodoList() {
5 const [todos, setTodos] = useState([]);
6 const [inputValue, setInputValue] = useState('');
7
8 const addTodo = () => {
9 if (inputValue.trim() !== '') {
10 const newTodo = {
11 id: Date.now(),
12 text: inputValue,
13 completed: false
14 };
15 setTodos([...todos, newTodo]);
16 setInputValue('');
17 }
18 };
19
20 const handleKeyPress = (e) => {
21 if (e.key === 'Enter') {
22 addTodo();
23 }
24 };
25
26 const toggleTodo = (id) => {
27 setTodos(todos.map(todo =>
28 todo.id === id ? { ...todo, completed: !todo.completed } : todo
29 ));
30 };
31
32 const deleteTodo = (id) => {
33 setTodos(todos.filter(todo => todo.id !== id));
34 };
35
36 return (
37 <div className="todo-list">
38 <h1>My Todo List</h1>
39 <div className="input-group">
40 <input
41 type="text"
42 value={inputValue}
43 onChange={(e) => setInputValue(e.target.value)}
44 onKeyPress={handleKeyPress}
45 placeholder="Add a new todo..."
46 />
47 <button onClick={addTodo}>Add</button>
48 </div>
49 <ul>
50 {todos.map((todo) => (
51 <li key={todo.id} className={todo.completed ? 'completed' : ''}>
52 <input
53 type="checkbox"
54 checked={todo.completed}
55 onChange={() => toggleTodo(todo.id)}
56 />
57 <span>{todo.text}</span>
58 <button onClick={() => deleteTodo(todo.id)}>Delete</button>
59 </li>
60 ))}
61 </ul>
62 </div>
63 );
64}
65
66export default TodoList;

🚀 Extra Challenges

Ready to take this project further? Try these challenges:

  • Add a "Clear Completed" button that removes all completed todos
  • Implement local storage to persist todos between page refreshes
  • Add the ability to edit existing todos
  • Create categories or tags for todos
  • Add a filter to show all/active/completed todos