TODO LIST using React
Imports:
useEffect
anduseState
are imported from React for managing state and side-effects.'./App.css'
imports the stylesheet.TodoProvider
is imported from the context, which will be used to provide state and functions throughout the app.TodoForm
andTodoItem
are imported fromcomponents
for rendering the form and individual todo items.
State Initialization:
- The
todos
state is initialized as an empty arrayuseState([])
. This state will store the list of todos.
- The
Add Todo:
addTodo
: This function adds a new todo to thetodos
array.It uses
setTodos
to update the state.prev
refers to the previous state of the todos.The new todo is inserted at the front of the array (you can also add it to the back).
The
id
is generated usingDate.now
()
to create a unique identifier for each todo.After adding the todo, we use the spread operator (
...
) to include all properties of the new todo.
const addTodo = (todo) => {
setTodos((prev) => [{ id: Date.now(), ...todo }, ...prev]);
}
Update Todo:
updateTodo
: This function updates a todo by matching itsid
and replacing it with the new todo object.It uses
map
to iterate over thetodos
array.If the
id
matches, it updates the todo; otherwise, it keeps the old one.
const updateTodo = (id, todo) => {
setTodos((prev) => prev.map((prevTodo) => (prevTodo.id === id ? todo : prevTodo)));
}
Delete Todo:
deleteTodo
: This function removes a todo from the list based on itsid
.It uses
filter
to create a new array without the todo whoseid
matches.If the
id
is different, the todo remains in the array.
const deleteTodo = (id) => {
setTodos((prev) => prev.filter((todo) => todo.id !== id));
}
Toggle Todo Completion:
toggleComplete
: This function toggles thecompleted
property of a todo.It uses
map
to iterate over all todos, checking for the matchingid
.If the
id
matches, it toggles thecompleted
property.
const toggleComplete = (id) => {
setTodos((prev) => prev.map((prevTodo) =>
prevTodo.id === id ? { ...prevTodo, completed: !prevTodo.completed } : prevTodo
));
}
Local Storage:
The
useEffect
hook is used to interact with the browser's local storage.The first
useEffect
runs once when the component mounts, fetching any todos stored in local storage and setting the state (setTodos
).The second
useEffect
runs whenevertodos
changes, storing the current state in local storage as a stringified JSON object.
useEffect(() => {
const todos = JSON.parse(localStorage.getItem("todos"));
if (todos && todos.length > 0) {
setTodos(todos);
}
}, []);
useEffect(() => {
localStorage.setItem("todos", JSON.stringify(todos));
}, [todos]);
Provider:
The
TodoProvider
is used to wrap the app and provide thetodos
,addTodo
,updateTodo
,deleteTodo
, andtoggleComplete
functions to all components inside.The value prop destructures the functions and passes them down to child components.
<TodoProvider value={{ todos, addTodo, updateTodo, deleteTodo, toggleComplete }}>
{/* App components here */}
</TodoProvider>
Rendering Todos:
Inside the return statement, the
todos
array is mapped over to render eachTodoItem
.Each
TodoItem
receives atodo
prop, which contains the individual todo data.
{todos.map((todo) => (
<div key={todo.id} className="w-full">
<TodoItem todo={todo} />
</div>
))}
Complete Code:
import { useEffect, useState } from 'react';
import './App.css';
import { TodoProvider } from './context';
import { TodoForm, TodoItem } from './components';
function App() {
const [todos, setTodos] = useState([]);
const addTodo = (todo) => {
setTodos((prev) => [{ id: Date.now(), ...todo }, ...prev]);
};
const updateTodo = (id, todo) => {
setTodos((prev) => prev.map((prevTodo) => (prevTodo.id === id ? todo : prevTodo)));
};
const deleteTodo = (id) => {
setTodos((prev) => prev.filter((todo) => todo.id !== id));
};
const toggleComplete = (id) => {
setTodos((prev) =>
prev.map((prevTodo) => (prevTodo.id === id ? { ...prevTodo, completed: !prevTodo.completed } : prevTodo))
);
};
useEffect(() => {
const todos = JSON.parse(localStorage.getItem('todos'));
if (todos && todos.length > 0) {
setTodos(todos);
}
}, []);
useEffect(() => {
localStorage.setItem('todos', JSON.stringify(todos));
}, [todos]);
return (
<TodoProvider value={{ todos, addTodo, updateTodo, deleteTodo, toggleComplete }}>
<div className="bg-[#172842] min-h-screen py-8">
<div className="w-full max-w-2xl mx-auto shadow-md rounded-lg px-4 py-3 text-white">
<h1 className="text-2xl font-bold text-center mb-8 mt-2">Manage Your Todos</h1>
<div className="mb-4">
<TodoForm />
</div>
<div className="flex flex-wrap gap-y-3">
{todos.map((todo) => (
<div key={todo.id} className="w-full">
<TodoItem todo={todo} />
</div>
))}
</div>
</div>
</div>
</TodoProvider>
);
}
export default App;
Imports:
useContext
is imported fromreact
to allow components to access the context.createContext
is imported fromreact
to create a context for managing the todo list.
TodoContext:
TodoContext
is created usingcreateContext
with an initial value. This context will hold the state of the todos and provide various methods (like adding, updating, and deleting todos) to manipulate the todo list.The default state includes a
todos
array with a sample todo item that has properties:id
,todo
(the task description), andcompleted
(which tracks if the task is marked as completed).
Explanation of default state:
todos
: Contains an array with todo objects. Each todo is an object with:id
: A unique identifier for each todo.todo
: The text or message of the todo item.completed
: A boolean flag (defaulted tofalse
) to represent whether the todo is completed or not.
Methods:
addTodo(todo)
: Function to add a new todo.updateTodo(id, todo)
: Function to update an existing todo based on its ID.deleteTodo(id)
: Function to delete a todo based on its ID.toggleComplete(id)
: Function to toggle thecompleted
status of a todo.
useTodo
Hook:useTodo
: A custom hook that simplifies the process of consuming theTodoContext
within any component. It callsuseContext
withTodoContext
to return the context value, allowing components to access the todos and the methods for manipulating them.
TodoProvider:
TodoProvider
: This is theProvider
component fromTodoContext
used to wrap the application (or a portion of it) that needs access to the todo context. It passes the context value (todos and methods) down to all components within the provider.
Complete Code:
import React from "react";
import { useContext } from "react";
import { createContext } from "react";
// There are no databases; we're storing data in local storage
// We need to create an ID for each todo entry.
export const TodoContext = createContext({
// Every todo will be an object.
// Creating an initial 'todos' array with a sample todo.
todos: [
{
id: 1,
todo: "Todo msg", // Initial todo text
completed: false, // Default status is not completed
}
],
// Functions to manage the todos are placeholders here.
// These are implemented in the parent component (e.g., App.jsx).
addTodo: (todo) => {},
updateTodo: (id, todo) => {},
deleteTodo: (id) => {},
toggleComplete: (id) => {}
});
// Custom hook to access the TodoContext
export const useTodo = () => {
return useContext(TodoContext);
}
// TodoProvider is the Provider that will wrap the components needing access to the todo context.
export const TodoProvider = TodoContext.Provider;
Imports:
The
useState
hook is imported from React to manage local state.The
useTodo
custom hook is imported from the context file to access todo-related functions (update, delete, toggle) from the context.
Component Structure:
TodoItem
: This component represents a single todo item. It receives thetodo
object as a prop, which contains the todo's details likeid
,todo
(task), andcompleted
(status).
State Management:
isTodoEditable
: A state that tracks whether the todo item is in edit mode (true
) or not (false
).todoMsg
: A state that holds the current message of the todo. This is initialized with the value oftodo.todo
(the text in the todo item) and can be edited.
Context Functions:
updateTodo
,deleteTodo
, andtoggleComplete
are functions obtained from theuseTodo
context. These functions allow updating, deleting, and toggling the completion status of todos.
Edit Todo:
- The
editTodo
function is triggered when the user clicks on the edit button (pencil icon). This function updates the todo with the newtodoMsg
(text entered by the user) and then sets theisTodoEditable
state tofalse
, making the todo non-editable.
- The
Toggle Completed:
toggleCompleted
: This function is triggered when the user clicks the checkbox. It calls thetoggleComplete
function from the context, which toggles thecompleted
status of the todo.
Rendering:
The component renders a
div
containing:A checkbox to mark the todo as completed or not.
A text input to display and edit the todo's message.
Two buttons:
Edit/Save Button: This toggles between edit mode and save mode. If the todo is completed, this button is disabled.
Delete Button: A button to delete the todo, which calls
deleteTodo
with the todo'sid
.
Dynamic Styling:
The
div
and input fields have dynamic styles based on thecompleted
status of the todo:If the todo is completed, the background color changes to a light green (
bg-[#c6e9a7]
), and the input field has aline-through
style.If the todo is not completed, it uses a different background color (
bg-[#ccbed7]
).
Complete Code:
import React, { useState } from 'react';
import { useTodo } from '../context';
function TodoItem({ todo }) {
console.log("Adding todo:", { id: Date.now(), todo: todo, completed: false });
// State for tracking whether the todo is editable
const [isTodoEditable, setIsTodoEditable] = useState(false);
// State for holding the todo message
const [todoMsg, setTodoMsg] = useState(todo.todo);
const { updateTodo, deleteTodo, toggleComplete } = useTodo();
const editTodo = () => {
// Update the todo with the new message
updateTodo(todo.id, { ...todo, todo: todoMsg });
// Set the todo as not editable after saving
setIsTodoEditable(false);
};
const toggleCompleted = () => {
// Toggle the completion status of the todo
console.log(todo.id);
toggleComplete(todo.id);
};
return (
<div
className={`flex border border-black/10 rounded-lg px-3 py-1.5 gap-x-3 shadow-sm shadow-white/50 duration-300 text-black ${
todo.completed ? "bg-[#c6e9a7]" : "bg-[#ccbed7]"
}`}
>
<input
type="checkbox"
className="cursor-pointer"
checked={todo.completed}
onChange={toggleCompleted}
/>
<input
type="text"
className={`border outline-none w-full bg-transparent rounded-lg ${
isTodoEditable ? "border-black/10 px-2" : "border-transparent"
} ${todo.completed ? "line-through" : ""}`}
value={todoMsg}
onChange={(e) => setTodoMsg(e.target.value)}
readOnly={!isTodoEditable}
/>
{/* Edit, Save Button */}
<button
className="inline-flex w-8 h-8 rounded-lg text-sm border border-black/10 justify-center items-center bg-gray-50 hover:bg-gray-100 shrink-0 disabled:opacity-50"
onClick={() => {
if (todo.completed) return;
if (isTodoEditable) {
editTodo();
} else setIsTodoEditable((prev) => !prev);
}}
disabled={todo.completed}
>
{isTodoEditable ? "๐" : "โ๏ธ"}
</button>
{/* Delete Todo Button */}
<button
className="inline-flex w-8 h-8 rounded-lg text-sm border border-black/10 justify-center items-center bg-gray-50 hover:bg-gray-100 shrink-0"
onClick={() => deleteTodo(todo.id)}
>
โ
</button>
</div>
);
}
export default TodoItem;
Imports:
The
useState
hook is imported from React to manage local state.The
useTodo
custom hook is imported from the context file to access theaddTodo
function from the context.
Component Structure:
TodoForm
: This component is responsible for managing the form input, where users can add new todos.
State Management:
todo
: This state holds the value of the todo input field. Initially, it's an empty string.
Context Functions:
addTodo
: This function is obtained from theuseTodo
context, and it allows adding a new todo to the list.
Add Todo:
The
add
function is triggered when the form is submitted. It first prevents the default form submission behavior usinge.preventDefault()
.If the
todo
input is empty, the function returns early, preventing the todo from being added.Otherwise, it calls the
addTodo
function, passing an object with thetodo
text and thecompleted
status set tofalse
. Theid
is automatically set toDate.now
()
for a unique identifier.After adding the todo, the input field is cleared by setting
setTodo("")
.
Rendering:
The component renders a form with:
An input field for entering the todo.
A submit button to add the todo to the list.
Dynamic Styling:
- The input and button elements are styled using Tailwind CSS classes to give a clean and responsive layout.
Complete Code:
import React, { useState } from 'react';
import { useTodo } from '../context';
function TodoForm() {
// State to track the value of the todo input
const [todo, setTodo] = useState("");
// Accessing the addTodo function from the context
const { addTodo } = useTodo();
// Method to handle form submission
const add = (e) => {
e.preventDefault();
// If todo is empty, do nothing
if (!todo) return;
// Adding the new todo to the list (with a generated id)
addTodo({ todo, completed: false });
// Clear the input field after adding the todo
setTodo("");
};
return (
<form onSubmit={add} className="flex">
<input
type="text"
placeholder="Write Todo..."
className="w-full border border-black/10 rounded-l-lg px-3 outline-none duration-150 bg-white/20 py-1.5"
value={todo}
// Update todo state as the user types
onChange={(e) => setTodo(e.target.value)}
/>
<button type="submit" className="rounded-r-lg px-3 py-1 bg-green-600 text-white shrink-0">
Add
</button>
</form>
);
}
export default TodoForm;