React TODO Comprehensive project
Building a React To-Do app with React Hooks is a great way to learn how to use React Hooks in your projects. React Hooks are a new feature introduced in React 16.8 that allows you to use state and other React features without writing a class component.
Here are the steps to build a React To-Do app with React Hooks:
Create a new React project
Here are the steps to create a new React project using create-react-app
:
- Open your terminal or command prompt.
- Navigate to the directory where you want to create your new React project.
- Run the following command to create a new React project:
npx create-react-app my-project
Replace my-project
with the name of your project. This command will create a new directory called my-project
(or whatever you named your project) in your current directory and set up a new React project inside it.
4. Once the command has finished running, navigate into the new project directory by running:
cd my-project
That's it! You have successfully created a new React project using create-react-app
. You can now start developing your app by running npm start
, which will start the development server and open your app in a new browser tab.
Set up the basic structure of the app, including creating a TodoList component and a Todo component using functional programming with hooks
- Open your code editor and navigate to the root directory of your React project.
- Create a new file called
TodoList.js
in thesrc
directory. - Open
TodoList.js
and import the necessary hooks from thereact
library:
4. Create a functional component called TodoList
:
5. Inside the TodoList
component, create state to store the list of todos using the useState
hook:
6. Create a new file called Todo.js
in the src
directory.
7. Open Todo.js
and create a functional component called Todo
:
8. Export both the TodoList
and Todo
components by adding the following lines at the bottom of each file:
and
That's it! You have now set up the basic structure of a Todo List app using functional programming and hooks. You can now start adding more functionality to your app by implementing the TodoList
and Todo
components.
In the TodoList component, create state to store the list of todos.
Here's an example of how to create state to store the list of todos in the TodoList
component using the useState
hook.
import React, { useState } from 'react';
function TodoList() {
const [todos, setTodos] = useState([]);
return (
// Your JSX code goes here
);
}
export default TodoList;
In the example above, we first import the useState
hook from the react
library. Then, inside the TodoList
component, we call the useState
hook and pass an empty array as the initial state value. The useState
hook returns an array containing two elements: the current state value (todos
) and a function to update the state value (setTodos
).
We then use destructuring to assign the todos
state value to a variable called todos
, and the setTodos
function to a variable called setTodos
.
You can now use the todos
state value to render your list of todos and the setTodos
function to update the list when necessary.
Create a form in the TodoList component to add new todos. This form should have an input field for the todo text and a button to add the todo to the list.
Here's an example of how to create a form in the TodoList
component to add new todos:
import React, { useState } from 'react';
function TodoList() {
const [todos, setTodos] = useState([]);
const [todoInput, setTodoInput] = useState('');
const handleInputChange = (e) => {
setTodoInput(e.target.value);
};
const handleAddTodo = () => {
if (todoInput.trim()) {
setTodos([...todos, { id: Date.now(), text: todoInput, completed: false }]);
setTodoInput('');
}
};
return (
<div>
<form onSubmit={(e) => { e.preventDefault(); handleAddTodo(); }}>
<input type="text" value={todoInput} onChange={handleInputChange} />
<button type="submit">Add Todo</button>
</form>
{/* Your code to render the list of todos goes here */}
</div>
);
}
export default TodoList;
In the example above, we first add a new state value called todoInput
using the useState
hook. This state value will store the current value of the input field.
We then create two new functions: handleInputChange
and handleAddTodo
.
The handleInputChange
function is called every time the input field changes, and updates the todoInput
state value to the new value of the input field.
The handleAddTodo
function is called when the form is submitted. It checks if the todoInput
value is not empty (using trim()
to remove leading and trailing whitespace), creates a new todo object with a unique id
, the text
from the input field, and a default value of completed: false
, and adds it to the todos
state array using the spread operator ([...todos, newTodo]
). Finally, it resets the todoInput
value to an empty string.
We then render a form with an input field for the todo text and a button to add the todo to the list. The onSubmit
handler calls handleAddTodo
to add the new todo to the list.
Note that the actual rendering of the list of todos is not shown in this example, but you can use the todos
state value to render the list of todos in whatever way you like.
Handle the form submission and add the new todo to the list in the state.
Here's an example of how to handle the form submission and add the new todo to the list in the state:
import React, { useState } from 'react';
function TodoList() {
const [todos, setTodos] = useState([]);
const [todoInput, setTodoInput] = useState('');
const handleInputChange = (e) => {
setTodoInput(e.target.value);
};
const handleAddTodo = (e) => {
e.preventDefault();
if (todoInput.trim()) {
setTodos([...todos, { id: Date.now(), text: todoInput, completed: false }]);
setTodoInput('');
}
};
return (
<div>
<form onSubmit={handleAddTodo}>
<input type="text" value={todoInput} onChange={handleInputChange} />
<button type="submit">Add Todo</button>
</form>
{/* Your code to render the list of todos goes here */}
</div>
);
}
export default TodoList;
In the example above, we first update the handleAddTodo
function to take an argument of e
(the event object) and call preventDefault()
to prevent the form from submitting and refreshing the page.
We then check if the todoInput
value is not empty (using trim()
to remove leading and trailing whitespace), create a new todo object with a unique id
, the text
from the input field, and a default value of completed: false
, and add it to the todos
state array using the spread operator ([...todos, newTodo]
). Finally, we reset the todoInput
value to an empty string.
Now when the form is submitted, the handleAddTodo
function will be called, the new todo will be added to the todos
state array, and the input field will be cleared.
Map over the list of todos in the state and render a Todo component for each todo. The Todo component should display the todo text and a delete button.
Here's an example of how to map over the list of todos in the state and render a Todo component for each todo:
import React, { useState } from 'react';
function TodoList() {
const [todos, setTodos] = useState([]);
const [todoInput, setTodoInput] = useState('');
const handleInputChange = (e) => {
setTodoInput(e.target.value);
};
const handleAddTodo = (e) => {
e.preventDefault();
if (todoInput.trim()) {
setTodos([...todos, { id: Date.now(), text: todoInput, completed: false }]);
setTodoInput('');
}
};
const handleDeleteTodo = (id) => {
setTodos(todos.filter((todo) => todo.id !== id));
};
return (
<div>
<form onSubmit={handleAddTodo}>
<input type="text" value={todoInput} onChange={handleInputChange} />
<button type="submit">Add Todo</button>
</form>
{todos.map((todo) => (
<Todo key={todo.id} todo={todo} onDelete={handleDeleteTodo} />
))}
</div>
);
}
function Todo({ todo, onDelete }) {
return (
<div>
<p>{todo.text}</p>
<button onClick={() => onDelete(todo.id)}>Delete</button>
</div>
);
}
export default TodoList;
In the example above, we first add a new function called handleDeleteTodo
, which takes an argument of id
and filters the todos
state array to remove the todo with that id
.
We then map over the todos
state array and render a Todo
component for each todo. The Todo
component takes two props: todo
, which is the todo object, and onDelete
, which is a function that will be called when the delete button is clicked.
The Todo
component displays the todo text using todo.text
, and renders a delete button that calls the onDelete
function with the id
of the todo when clicked.
Now when a new todo is added to the todos
state array, it will be displayed as a Todo
component with a delete button. Clicking the delete button will remove the todo from the todos
state array.
Add functionality to delete todos from the list when the delete button is clicked
In order to add functionality to delete todos from the list when the delete button is clicked, you can pass a handleDeleteTodo
function from the TodoList
component down to the Todo
component as a prop, and then call it when the delete button is clicked. Here's an example:
import React, { useState } from 'react';
function TodoList() {
const [todos, setTodos] = useState([]);
const [todoInput, setTodoInput] = useState('');
const handleInputChange = (e) => {
setTodoInput(e.target.value);
};
const handleAddTodo = (e) => {
e.preventDefault();
if (todoInput.trim()) {
setTodos([...todos, { id: Date.now(), text: todoInput, completed: false }]);
setTodoInput('');
}
};
const handleDeleteTodo = (id) => {
setTodos(todos.filter((todo) => todo.id !== id));
};
return (
<div>
<form onSubmit={handleAddTodo}>
<input type="text" value={todoInput} onChange={handleInputChange} />
<button type="submit">Add Todo</button>
</form>
{todos.map((todo) => (
<Todo key={todo.id} todo={todo} handleDeleteTodo={handleDeleteTodo} />
))}
</div>
);
}
function Todo({ todo, handleDeleteTodo }) {
const handleDeleteClick = () => {
handleDeleteTodo(todo.id);
};
return (
<div>
<p>{todo.text}</p>
<button onClick={handleDeleteClick}>Delete</button>
</div>
);
}
export default TodoList;
In this example, we pass the handleDeleteTodo
function from the TodoList
component down to the Todo
component as a prop called handleDeleteTodo
. We then define a new function called handleDeleteClick
in the Todo
component, which calls the handleDeleteTodo
function with the id
of the todo when the delete button is clicked.
When a user clicks the delete button on a todo, the handleDeleteClick
function is called, which in turn calls the handleDeleteTodo
function passed down from the TodoList
component with the id
of the todo. The handleDeleteTodo
function removes the todo with that id
from the todos
state array, causing the TodoList
component to re-render with the updated list of todos.
Add functionality to mark todos as completed when they are clicked on. This can be done by adding a completed property to the todo object in the state and toggling it when the todo is clicked.
Here's an example of how you can add functionality to mark todos as completed when they are clicked on:
import React, { useState } from 'react';
function TodoList() {
const [todos, setTodos] = useState([]);
const [todoInput, setTodoInput] = useState('');
const handleInputChange = (e) => {
setTodoInput(e.target.value);
};
const handleAddTodo = (e) => {
e.preventDefault();
if (todoInput.trim()) {
setTodos([...todos, { id: Date.now(), text: todoInput, completed: false }]);
setTodoInput('');
}
};
const handleDeleteTodo = (id) => {
setTodos(todos.filter((todo) => todo.id !== id));
};
const handleTodoClick = (id) => {
setTodos(
todos.map((todo) =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
)
);
};
return (
<div>
<form onSubmit={handleAddTodo}>
<input type="text" value={todoInput} onChange={handleInputChange} />
<button type="submit">Add Todo</button>
</form>
{todos.map((todo) => (
<Todo
key={todo.id}
todo={todo}
handleDeleteTodo={handleDeleteTodo}
handleTodoClick={handleTodoClick}
/>
))}
</div>
);
}
function Todo({ todo, handleDeleteTodo, handleTodoClick }) {
const handleDeleteClick = () => {
handleDeleteTodo(todo.id);
};
const handleTodoClick = () => {
handleTodoClick(todo.id);
};
return (
<div>
<p
style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}
onClick={handleTodoClick}
>
{todo.text}
</p>
<button onClick={handleDeleteClick}>Delete</button>
</div>
);
}
export default TodoList;
In this example, we've added a new function called handleTodoClick
in the TodoList
component that updates the completed
property of the todo with the given id
in the todos
state array. We pass this function down to the Todo
component as a prop called handleTodoClick
.
In the Todo
component, we call the handleTodoClick
function when the todo text is clicked, and toggle the textDecoration
style based on the completed
property of the todo. When a user clicks on a todo, the handleTodoClick
function is called with the id
of the todo. This function maps over the todos
state array and updates the completed
property of the todo with the given id
by creating a new todo object with the same properties as the original todo object, except with the completed
property set to the opposite value.
Add styling to make the app look nice and user-friendly.
Here's an example of how you can add some basic styling to make the app look nice and user-friendly:
import React, { useState } from 'react';
import './App.css';
function TodoList() {
const [todos, setTodos] = useState([]);
const [todoInput, setTodoInput] = useState('');
const handleInputChange = (e) => {
setTodoInput(e.target.value);
};
const handleAddTodo = (e) => {
e.preventDefault();
if (todoInput.trim()) {
setTodos([...todos, { id: Date.now(), text: todoInput, completed: false }]);
setTodoInput('');
}
};
const handleDeleteTodo = (id) => {
setTodos(todos.filter((todo) => todo.id !== id));
};
const handleTodoClick = (id) => {
setTodos(
todos.map((todo) =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
)
);
};
return (
<div className="container">
<h1 className="title">Todo List</h1>
<form onSubmit={handleAddTodo}>
<input type="text" value={todoInput} onChange={handleInputChange} className="input-field" placeholder="Enter your task" />
<button type="submit" className="add-button">Add</button>
</form>
{todos.map((todo) => (
<Todo
key={todo.id}
todo={todo}
handleDeleteTodo={handleDeleteTodo}
handleTodoClick={handleTodoClick}
/>
))}
</div>
);
}
function Todo({ todo, handleDeleteTodo, handleTodoClick }) {
const handleDeleteClick = () => {
handleDeleteTodo(todo.id);
};
const handleTodoClick = () => {
handleTodoClick(todo.id);
};
return (
<div className="todo-container">
<p
className={todo.completed ? 'completed' : ''}
onClick={handleTodoClick}
>
{todo.text}
</p>
<button onClick={handleDeleteClick} className="delete-button">Delete</button>
</div>
);
}
export default TodoList;
In this example, we've added a few new CSS classes to style the app. We've added a container
class to the main app container to center it on the page and give it a nice background color. We've also added a title
class to the app title to make it stand out.
We've added some styles to the form inputs to make them look more user-friendly. We've given the input field a placeholder
text to prompt the user to enter their task. We've also added a add-button
class to the add button to make it stand out and added a delete-button
class to the delete button to give it a different color.
Finally, we've added some styles to the Todo
component to make it look more visually appealing. We've given the component a todo-container
class to center it on the page and added a border to give it a more defined look. We've also added a completed
class to the todo text when it's completed to give it a line-through effect.
These are just a few examples of how you can style the app. You can customize the styles further to suit your own preferences and design.
Test the app to make sure it is functioning as expected.
Here are some tests you can perform to ensure the app is functioning as expected:
- Add a new todo using the form. Verify that the new todo is added to the list.
- Try adding an empty todo by submitting the form with an empty input field. Verify that no new todo is added to the list.
- Click the delete button on a todo. Verify that the todo is removed from the list.
- Click on a todo text to mark it as completed. Verify that the text is striked out and the todo is marked as completed.
- Click on a completed todo text to mark it as incomplete. Verify that the strikethrough effect is removed and the todo is marked as incomplete.
- Add several todos to the list, some of which are completed. Verify that the todos are displayed in the order they were added.
By performing these tests, you can ensure that the app is working as expected and meets the requirements of a basic todo list app.