Introduction
A Todo Application is one of the best practical projects for learning React because it combines components, state, events, arrays, forms, conditional rendering, and immutable updates. In this chapter, we will build Todo features step by step, starting with displaying a simple todo and gradually creating a complete Todo application with add, delete, update, complete, and filter functionality. React js Todo Application practice questions with solutions to help you understand the concepts.
1. How to Create a Basic Todo List in React?
Problem Statement:
Create a React component that displays a simple list of three todo items.
React Solution:
function TodoApp() {
const todos = [
"Learn React",
"Practice JavaScript",
"Build a project"
];
return (
<div>
<h1>Todo List</h1>
{todos.map((todo, index) => (
<p key={index}>{todo}</p>
))}
</div>
);
}
export default TodoApp;
Output:
Todo List
Learn React
Practice JavaScript
Build a project
Explanation:
The todos are stored in an array and displayed using map(). Each item needs a key when rendering a list. For a real application, stable unique IDs are preferred over array indexes.
Concepts Covered:
- Arrays
map()- List Rendering
- Keys
2. How to Add a Todo Using useState?
Problem Statement:
Create a Todo application where the user can type a todo and add it to the list.
React Solution:
import { useState } from "react";
function TodoApp() {
const [todo, setTodo] = useState("");
const [todos, setTodos] = useState([]);
function addTodo() {
if (todo.trim() === "") {
return;
}
setTodos((currentTodos) => [
...currentTodos,
todo
]);
setTodo("");
}
return (
<div>
<h1>Todo List</h1>
<input
type="text"
value={todo}
onChange={(event) => setTodo(event.target.value)}
placeholder="Enter a todo"
/>
<button onClick={addTodo}>
Add Todo
</button>
{todos.map((item, index) => (
<p key={index}>{item}</p>
))}
</div>
);
}
export default TodoApp;
Output:
Todo List
[ Enter a todo ] [Add Todo]
Learn React
Practice JavaScript
Explanation:
The input value is controlled by the todo state. When the button is clicked, the new todo is added to the todos array using the spread operator. The input is then cleared.
Concepts Covered:
useState- Controlled Input
onChange- Array State
- Spread Operator
3. How to Add a Todo with a Form in React?
Problem Statement:
Use a form instead of a button-only approach to add todos when the user submits the form.
React Solution:
import { useState } from "react";
function TodoApp() {
const [todo, setTodo] = useState("");
const [todos, setTodos] = useState([]);
function handleSubmit(event) {
event.preventDefault();
if (todo.trim() === "") {
return;
}
setTodos((currentTodos) => [
...currentTodos,
todo
]);
setTodo("");
}
return (
<div>
<h1>Todo List</h1>
<form onSubmit={handleSubmit}>
<input
value={todo}
onChange={(event) => setTodo(event.target.value)}
placeholder="Enter a todo"
/>
<button type="submit">
Add Todo
</button>
</form>
{todos.map((item, index) => (
<p key={index}>{item}</p>
))}
</div>
);
}
export default TodoApp;
Output:
Todo List
[ Enter a todo ] [Add Todo]
Learn React
Build a project
Explanation:
The onSubmit event handles the form submission. event.preventDefault() prevents the browser from refreshing the page. The todo is added only when it contains meaningful text.
Concepts Covered:
- Forms
onSubmitpreventDefault()- Controlled Components
- State Updates
4. How to Delete a Todo from a React Todo List?
Problem Statement:
Add a Delete button next to every todo and remove the selected todo from the list.
React Solution:
import { useState } from "react";
function TodoApp() {
const [todos, setTodos] = useState([
{ id: 1, text: "Learn React" },
{ id: 2, text: "Practice JavaScript" },
{ id: 3, text: "Build a project" }
]);
function deleteTodo(id) {
setTodos((currentTodos) =>
currentTodos.filter((todo) => todo.id !== id)
);
}
return (
<div>
<h1>Todo List</h1>
{todos.map((todo) => (
<div key={todo.id}>
<span>{todo.text}</span>
<button onClick={() => deleteTodo(todo.id)}>
Delete
</button>
</div>
))}
</div>
);
}
export default TodoApp;
Output:
Todo List
Learn React [Delete]
Practice JavaScript [Delete]
Build a project [Delete]
After deleting “Practice JavaScript”:
Todo List
Learn React [Delete]
Build a project [Delete]
Explanation:
The filter() method creates a new array without the selected todo. The original state array is not directly mutated.
Each todo has a stable id, which is also used as the React list key.
Concepts Covered:
filter()- Event Handling
- Array State
- Immutable Updates
- List Keys
5. How to Mark a Todo as Completed?
Problem Statement:
Add a checkbox that allows the user to mark a todo as completed.
React Solution:
import { useState } from "react";
function TodoApp() {
const [todos, setTodos] = useState([
{ id: 1, text: "Learn React", completed: false },
{ id: 2, text: "Practice JavaScript", completed: false }
]);
function toggleTodo(id) {
setTodos((currentTodos) =>
currentTodos.map((todo) =>
todo.id === id
? { ...todo, completed: !todo.completed }
: todo
)
);
}
return (
<div>
<h1>Todo List</h1>
{todos.map((todo) => (
<div key={todo.id}>
<label>
<input
type="checkbox"
checked={todo.completed}
onChange={() => toggleTodo(todo.id)}
/>
{todo.text}
</label>
</div>
))}
</div>
);
}
export default TodoApp;
Output:
Todo List
☐ Learn React
☐ Practice JavaScript
After checking the first todo:
Todo List
☑ Learn React
☐ Practice JavaScript
Explanation:
Each todo has a completed property. When the checkbox changes, map() creates a new array and updates only the selected todo.
The checkbox uses checked because it is a controlled input.
Concepts Covered:
- Checkbox
checkedmap()- Object Spread
- State Updates
6. How to Show Completed and Pending Todo Counts?
Problem Statement:
Display the total number of todos, completed todos, and pending todos.
React Solution:
import { useState } from "react";
function TodoApp() {
const [todos, setTodos] = useState([
{ id: 1, text: "Learn React", completed: true },
{ id: 2, text: "Practice JavaScript", completed: false },
{ id: 3, text: "Build a project", completed: true }
]);
const completedCount = todos.filter(
(todo) => todo.completed
).length;
const pendingCount = todos.filter(
(todo) => !todo.completed
).length;
return (
<div>
<h1>Todo List</h1>
<p>Total: {todos.length}</p>
<p>Completed: {completedCount}</p>
<p>Pending: {pendingCount}</p>
</div>
);
}
export default TodoApp;
Output:
Todo List
Total: 3
Completed: 2
Pending: 1
Explanation:
The counts are calculated from the existing todos state. There is no need to create separate state variables for these values because they can be derived from the current todo array.
Concepts Covered:
filter()- Derived Values
- Array Methods
- State
7. How to Edit a Todo in React?
Problem Statement:
Add an Edit button that allows the user to change an existing todo.
React Solution:
import { useState } from "react";
function TodoApp() {
const [todos, setTodos] = useState([
{ id: 1, text: "Learn React" },
{ id: 2, text: "Practice JavaScript" }
]);
const [editingId, setEditingId] = useState(null);
const [editText, setEditText] = useState("");
function startEditing(todo) {
setEditingId(todo.id);
setEditText(todo.text);
}
function saveTodo(id) {
setTodos((currentTodos) =>
currentTodos.map((todo) =>
todo.id === id
? { ...todo, text: editText }
: todo
)
);
setEditingId(null);
setEditText("");
}
return (
<div>
<h1>Todo List</h1>
{todos.map((todo) => (
<div key={todo.id}>
{editingId === todo.id ? (
<>
<input
value={editText}
onChange={(event) =>
setEditText(event.target.value)
}
/>
<button onClick={() => saveTodo(todo.id)}>
Save
</button>
</>
) : (
<>
<span>{todo.text}</span>
<button onClick={() => startEditing(todo)}>
Edit
</button>
</>
)}
</div>
))}
</div>
);
}
export default TodoApp;
Output:
Learn React [Edit]
Practice JavaScript [Edit]
After clicking Edit:
[Learn React] [Save]
Explanation:editingId keeps track of which todo is currently being edited. editText stores the temporary input value. When Save is clicked, map() updates only the selected todo.
Concepts Covered:
- Conditional Rendering
useStatemap()- Object Updates
- Controlled Input
8. How to Filter Todos by All, Active, and Completed?
Problem Statement:
Create buttons that allow users to view all todos, active todos, or completed todos.
React Solution:
import { useState } from "react";
function TodoApp() {
const [todos] = useState([
{ id: 1, text: "Learn React", completed: true },
{ id: 2, text: "Practice JavaScript", completed: false },
{ id: 3, text: "Build a project", completed: true }
]);
const [filter, setFilter] = useState("all");
const filteredTodos = todos.filter((todo) => {
if (filter === "active") {
return !todo.completed;
}
if (filter === "completed") {
return todo.completed;
}
return true;
});
return (
<div>
<h1>Todo List</h1>
<button onClick={() => setFilter("all")}>
All
</button>
<button onClick={() => setFilter("active")}>
Active
</button>
<button onClick={() => setFilter("completed")}>
Completed
</button>
{filteredTodos.map((todo) => (
<p key={todo.id}>{todo.text}</p>
))}
</div>
);
}
export default TodoApp;
Output:
[All] [Active] [Completed]
Learn React
Practice JavaScript
Build a project
Selecting Active:
Practice JavaScript
Selecting Completed:
Learn React
Build a project
Explanation:
The filter state stores the selected filter type. filteredTodos is derived from the existing todo data. It does not need to be stored separately in state.
Concepts Covered:
- Filtering
- Conditional Logic
- Derived Data
useStatefilter()
9. How to Save Todos in Local Storage?
Problem Statement:
Save the Todo list in browser Local Storage so that todos remain available after refreshing the page.
React Solution:
import { useEffect, useState } from "react";
function TodoApp() {
const [todos, setTodos] = useState(() => {
const savedTodos = localStorage.getItem("todos");
return savedTodos
? JSON.parse(savedTodos)
: [];
});
useEffect(() => {
localStorage.setItem(
"todos",
JSON.stringify(todos)
);
}, [todos]);
return (
<div>
<h1>Todo List</h1>
{todos.map((todo) => (
<p key={todo.id}>{todo.text}</p>
))}
</div>
);
}
export default TodoApp;
Output:
Todo List
Learn React
Practice JavaScript
Build a project
After refreshing the browser, the stored todos can be loaded again.
Explanation:
Local Storage stores data as strings, so JSON.stringify() is used when saving an array and JSON.parse() is used when reading it.
The lazy initializer passed to useState reads the stored data when the state is initialized. useEffect keeps Local Storage synchronized whenever todos changes.
For real applications, avoid storing passwords or other sensitive authentication secrets in Local Storage.
Concepts Covered:
- Local Storage
useEffectuseStateJSON.stringify()JSON.parse()- Persistent Client-Side Data
10. How to Build a Complete React Todo Application?
Problem Statement:
Build a practical Todo application with the following features:
- Add Todo
- Delete Todo
- Mark Todo as completed
- Show All, Active, and Completed todos
- Display todo counts
React Solution:
import { useState } from "react";
function TodoApp() {
const [text, setText] = useState("");
const [todos, setTodos] = useState([]);
const [filter, setFilter] = useState("all");
function addTodo(event) {
event.preventDefault();
if (text.trim() === "") {
return;
}
const newTodo = {
id: crypto.randomUUID(),
text: text.trim(),
completed: false
};
setTodos((currentTodos) => [
...currentTodos,
newTodo
]);
setText("");
}
function deleteTodo(id) {
setTodos((currentTodos) =>
currentTodos.filter((todo) => todo.id !== id)
);
}
function toggleTodo(id) {
setTodos((currentTodos) =>
currentTodos.map((todo) =>
todo.id === id
? { ...todo, completed: !todo.completed }
: todo
)
);
}
const filteredTodos = todos.filter((todo) => {
if (filter === "active") {
return !todo.completed;
}
if (filter === "completed") {
return todo.completed;
}
return true;
});
const completedCount = todos.filter(
(todo) => todo.completed
).length;
const activeCount = todos.length - completedCount;
return (
<div>
<h1>Todo Application</h1>
<form onSubmit={addTodo}>
<input
type="text"
value={text}
onChange={(event) =>
setText(event.target.value)
}
placeholder="Enter a todo"
/>
<button type="submit">
Add Todo
</button>
</form>
<div>
<button onClick={() => setFilter("all")}>
All
</button>
<button onClick={() => setFilter("active")}>
Active
</button>
<button onClick={() => setFilter("completed")}>
Completed
</button>
</div>
<p>Total: {todos.length}</p>
<p>Active: {activeCount}</p>
<p>Completed: {completedCount}</p>
<div>
{filteredTodos.length === 0 ? (
<p>No todos found.</p>
) : (
filteredTodos.map((todo) => (
<div key={todo.id}>
<label>
<input
type="checkbox"
checked={todo.completed}
onChange={() => toggleTodo(todo.id)}
/>
{todo.text}
</label>
<button
onClick={() => deleteTodo(todo.id)}
>
Delete
</button>
</div>
))
)}
</div>
</div>
);
}
export default TodoApp;
Output:
Todo Application
[ Enter a todo ] [Add Todo]
[All] [Active] [Completed]
Total: 3
Active: 2
Completed: 1
☐ Learn React [Delete]
☑ Practice React [Delete]
☐ Build a Project [Delete]
Selecting Completed shows:
☑ Practice React [Delete]
Selecting Active shows:
☐ Learn React [Delete]
☐ Build a Project [Delete]
Explanation:
This example combines the major concepts needed for a basic Todo application.
The input is controlled using useState. A new todo is added using an immutable array update. crypto.randomUUID() creates a unique ID for each todo. filter() removes todos and creates filtered views, while map() updates the completed status.
The application also derives active and completed counts from the current todos state instead of storing duplicate count values.
In a larger application, you could split this component into reusable components such as TodoForm, TodoList, TodoItem, and TodoFilter.
Concepts Covered:
useState- Forms
- Controlled Components
- Event Handling
map()filter()- Conditional Rendering
- Immutable State Updates
- Derived Data
- List Keys
- Component Design
Key Takeaways
- A Todo Application is a useful project for practicing core React concepts.
useStatecan manage the input value and Todo list.- Controlled inputs keep form values synchronized with React state.
map()can be used to render and update Todo items.filter()can be used to delete items and create filtered views.- Object spread helps update a Todo without directly mutating the existing object.
- Stable unique IDs should be used as keys for Todo lists.
- Completed and active counts can be derived from the Todo array instead of being stored as duplicate state.
- Local Storage can be used to persist Todo data in the browser.
- A larger Todo application can be divided into reusable components such as
TodoForm,TodoList, andTodoItem.
FAQs
1. What is a Todo Application in React?
A Todo Application in React is a small project where users can create, display, update, complete, delete, and filter tasks using React components and state.
2. Which React Hook is commonly used for a Todo Application?
useState is commonly used to manage Todo data and form input. Other Hooks such as useEffect can be added when features like Local Storage synchronization are required.
3. How do I add a Todo in React?
You can store the input in state and use setTodos() with the spread operator to create a new array containing the new Todo.
4. How do I delete a Todo in React?
The filter() method can create a new array that excludes the Todo with the selected ID.
5. How do I mark a Todo as completed in React?
Store a completed property for each Todo and use map() with object spread to toggle that property without mutating the existing state.
6. Can I save a React Todo Application in Local Storage?
Yes. Todos can be converted to a JSON string using JSON.stringify() and stored with Local Storage. They can later be read using JSON.parse().
7. What React concepts are learned by building a Todo Application?
A Todo project can help you practice useState, forms, events, controlled components, arrays, map(), filter(), conditional rendering, immutable updates, derived data, and component composition.
Written by Shubhranshu Shekhar, who has trained 20000+ students in coding.
