Introduction
Todo list applications are a classic project for developers learning a new framework or language. They provide a practical way to understand core concepts like state management, user input handling, and component rendering. In this comprehensive guide, we’ll walk you through building a complete and functional todo list application using React. We’ll cover everything from setting up the project to persisting data in local storage, creating a polished user interface, and adding features like marking tasks as complete and deleting them.
Setting Up the Project
First, let’s create a new React project using Create React App. Open your terminal and run:
npx create-react-app react-todo-appcd react-todo-appThis command bootstraps a new React project with all the necessary dependencies and configurations. Once the installation is complete, you can start the development server:
npm startThis will open your application in your default browser, typically at http://localhost:3000.
Project Structure
Let’s create a simple project structure to keep our code organized:
react-todo-app/├── public/├── src/│ ├── components/│ │ ├── TodoForm.js│ │ ├── TodoItem.js│ │ └── TodoList.js│ ├── App.js│ ├── App.css│ └── index.js├── package.json└── README.mdHere’s a brief explanation of each file:
App.js: The main component that orchestrates the application.App.css: CSS file for the main application.components/: Directory to store reusable React components.TodoForm.js: Component for adding new todos.TodoItem.js: Component for displaying a single todo item.TodoList.js: Component to display the list of todos.
Creating the TodoForm Component
The TodoForm component is responsible for taking user input and adding new todo items to the list. Create a file named TodoForm.js inside the components directory with the following code:
import React, { useState } from 'react';
function TodoForm({ addTodo }) { const [value, setValue] = useState('');
const handleSubmit = e => { e.preventDefault(); if (!value) return; addTodo(value); setValue(''); };
return ( <form onSubmit={handleSubmit}> <input type="text" className="input" value={value} placeholder="Add Todo..." onChange={e => setValue(e.target.value)} /> </form> );}
export default TodoForm;This component uses the useState hook to manage the input value. The handleSubmit function prevents the default form submission behavior, checks if the input is not empty, calls the addTodo function (which will be passed down from the parent component), and resets the input field.
Creating the TodoItem Component
The TodoItem component displays a single todo item and handles marking it as complete or deleting it. Create a file named TodoItem.js inside the components directory with the following code:
import React from 'react';
function TodoItem({ todo, index, completeTodo, removeTodo }) { return ( <div className="todo" style={{ textDecoration: todo.isCompleted ? "line-through" : "" }} > {todo.text}
<div> <button onClick={() => completeTodo(index)}>Complete</button> <button onClick={() => removeTodo(index)}>x</button> </div> </div> );}
export default TodoItem;This component receives the todo object, its index in the list, and the completeTodo and removeTodo functions (passed from the parent) as props. It renders the todo text and two buttons: one to mark the todo as complete and another to remove it. The textDecoration style is conditionally applied based on the isCompleted property of the todo.
Creating the TodoList Component
The TodoList component renders the list of todo items using the TodoItem component. Create a file named TodoList.js inside the components directory. For simplicity, we’ll move the completeTodo and removeTodo functions to the App.js and pass them down
import React from 'react';import TodoItem from './TodoItem';
function TodoList({ todos, completeTodo, removeTodo }) { return ( <div className="todo-list"> {todos.map((todo, index) => ( <TodoItem key={index} index={index} todo={todo} completeTodo={completeTodo} removeTodo={removeTodo} /> ))} </div> );}
export default TodoList;This component receives the todos array and the completeTodo and removeTodo functions as props. It uses the map method to iterate over the todos array and render a TodoItem component for each todo. The key prop is important for React to efficiently update the list.
Integrating Components in App.js
Now, let’s integrate these components into our main App.js file.
import React, { useState, useEffect } from 'react';import TodoForm from './components/TodoForm';import TodoList from './components/TodoList';import './App.css';
function App() { const [todos, setTodos] = useState(() => { const savedTodos = localStorage.getItem('todos'); if (savedTodos) { return JSON.parse(savedTodos); } else { return []; } });
useEffect(() => { localStorage.setItem('todos', JSON.stringify(todos)); }, [todos]);
const addTodo = text => { const newTodos = [...todos, { text, isCompleted: false }]; setTodos(newTodos); };
const completeTodo = index => { const newTodos = [...todos]; newTodos[index].isCompleted = true; setTodos(newTodos); };
const removeTodo = index => { const newTodos = [...todos]; newTodos.splice(index, 1); setTodos(newTodos); };
return ( <div className="app"> <div className="todo-list"> <h1>Todo List</h1> <TodoForm addTodo={addTodo} /> <TodoList todos={todos} completeTodo={completeTodo} removeTodo={removeTodo} /> </div> </div> );}
export default App;Here’s what’s happening in this code:
- State Management: We use the
useStatehook to manage thetodosarray. The initial state is retrieved from local storage using a function that’s only called on the first render. If there are no saved todos, it starts with an empty array. - Local Storage Persistence: The
useEffecthook is used to persist thetodosarray in local storage whenever it changes. This ensures that the todos are saved even when the browser is closed or refreshed.JSON.stringifyconverts the array to a string for storage, andJSON.parseconverts it back when retrieved. addTodoFunction: This function adds a new todo object (containing the text and aisCompletedflag) to thetodosarray. It creates a new array using the spread operator (...) to avoid mutating the original state.completeTodoFunction: This function marks a todo as complete by setting itsisCompletedproperty totrue. It also creates a new array to avoid mutating the state.removeTodoFunction: This function removes a todo from thetodosarray using thesplicemethod. It also creates a new array.- Rendering: The component renders the
TodoFormandTodoListcomponents, passing the necessary props (likeaddTodo,todos,completeTodo, andremoveTodo).
Styling the Application
Let’s add some basic styling to our application using the App.css file.
.app { display: flex; justify-content: center; align-items: center; height: 100vh; background-color: #f0f0f0;}
.todo-list { background-color: white; padding: 20px; border-radius: 8px; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); width: 400px;}
.todo { display: flex; justify-content: space-between; align-items: center; padding: 10px; border-bottom: 1px solid #eee;}
.todo:last-child { border-bottom: none;}
.input { width: 100%; padding: 10px; border: 1px solid #ccc; border-radius: 4px; margin-bottom: 10px;}
button { background-color: #4CAF50; /* Green */ border: none; color: white; padding: 5px 10px; text-align: center; text-decoration: none; display: inline-block; font-size: 14px; cursor: pointer; border-radius: 4px; margin-left: 5px;}
button:hover { opacity: 0.8;}This CSS provides basic styling for the app container, the todo list, individual todo items, and the input field. Feel free to customize the styling to your liking.
Conclusion
Congratulations! You’ve successfully built a complete todo list application using React. This tutorial covered the basics of creating components, managing state, handling user input, and persisting data in local storage. You can now expand this application by adding more features, such as editing todos, filtering todos based on their completion status, or implementing drag-and-drop functionality. Experiment with different styling techniques to make the application visually appealing. The possibilities are endless! Remember to always focus on writing clean, maintainable, and well-documented code. Happy coding!
Ekbal's Blog