Roadmap to Build a React To-Do App with JSON Server
Features:
- CRUD Operations (Create, Read, Update, Delete).
- JSON Server for backend (db.json with at least 10 entries).
- Axios for API calls.
- React Router for navigation (e.g., Home, About, Task Details).
- Component-based architecture for scalability.
- User-friendly UI with React hooks like
useState
anduseEffect
.
Step 1: Setting up the Development Environment
To start building the React To-Do App with Vite and JSON Server, follow these detailed instructions:
1. Create a New React Project with Vite
Run the following commands in your terminal to set up a new React project:
npm create vite@latest react-todo-app --template react
cd react-todo-app
npm install
This creates a new React project named react-todo-app
with a React template.
2. Install Required Dependencies
To add the necessary packages for our app (Axios, React Router, and JSON Server), install them using the following command:
npm install axios react-router-dom json-server
3. Project Structure
Organize your files for a cleaner and scalable codebase. Your project folder structure will look like this initially:
react-todo-app/
│
├── public/
│ └── favicon.ico
│
├── src/
│ ├── App.jsx
│ ├── index.css
│ ├── main.jsx
│ └── assets/
│
├── package.json
├── vite.config.js
└── db.json <-- JSON Server data file
4. Setup JSON Server
- Create a
db.json
file in the project root (outside thesrc
folder). - Add the initial tasks to the file
{
"tasks": [
{ "id": 1, "title": "Create a react project", "completed": false, "date": "2022-01-06T05:23:00" },
{ "id": 2, "title": "Learn React", "completed": false, "date": "2022-01-06T05:22:00" },
{ "id": 3, "title": "Create a Todo App", "completed": true, "date": "2022-01-06T05:21:00" },
{ "id": 4, "title": "Practice React Hooks", "completed": false, "date": "2022-01-06T05:20:00" },
{ "id": 5, "title": "Understand React Router", "completed": false, "date": "2022-01-06T05:19:00" },
{ "id": 6, "title": "Setup JSON Server", "completed": true, "date": "2022-01-06T05:18:00" },
{ "id": 7, "title": "Learn Axios", "completed": false, "date": "2022-01-06T05:17:00" },
{ "id": 8, "title": "Build CRUD functionality", "completed": false, "date": "2022-01-06T05:16:00" },
{ "id": 9, "title": "Test Todo App", "completed": false, "date": "2022-01-06T05:15:00" },
{ "id": 10, "title": "Deploy the app", "completed": false, "date": "2022-01-06T05:14:00" }
]
}
This file acts as your mock database for the JSON Server.
5. Run JSON Server
Start the JSON Server on port 5000
to serve the db.json
file:
npx json-server --watch db.json --port 5000
You should see output like this:
\{^_^}/ hi!
Loading db.json
Done
Resources
http://localhost:5000/tasks
Watching...
6. Test JSON Server
- Open your browser and navigate to
http://localhost:5000/tasks
. - You should see a list of tasks in JSON format.
Example Output:
[
{
"id": 1,
"title": "Create a react project",
"completed": false,
"date": "2022-01-06T05:23:00"
},
{
"id": 2,
"title": "Learn React",
"completed": false,
"date": "2022-01-06T05:22:00"
},
...
]
7. Start the React Development Server
Run the following command to start the React development server:
npm run dev
You will see output similar to this:
VITE v4.0.0 ready in 400 ms
➜ Local: http://localhost:5173/
Open your browser and navigate to http://localhost:5173/
. This is where your React app will be running.
Step 2: Setting Up React Router and Basic Components
Now that we have our environment ready and JSON Server running, let’s move on to setting up React Router and creating some basic components.
1. Install React Router
We already installed react-router-dom
in the first step. If not, install it now:
npm install react-router-dom
2. Create Pages Folder
Let’s create pages for routing.
- Inside the
src
folder, create a new folder namedpages
src/
├── pages/
├── Home.jsx
├── About.jsx
└── TaskDetails.jsx
3. Create Home Page
The Home page will serve as the main page that displays the to-do list. Add the following code to Home.jsx
:
import React from "react";
function Home() {
return (
<div className="container">
<h1>TODO LIST</h1>
{/* Add TaskList and AddTask components here in the next steps */}
</div>
);
}
export default Home;
4. Create About Page
The About page will display a brief description of the app. Add the following code to About.jsx
:
import React from "react";
function About() {
return (
<div className="container">
<h1>About</h1>
<p>This is a simple To-Do App built with React, JSON Server, and Axios.</p>
</div>
);
}
export default About;
5. Create TaskDetails Page
The TaskDetails page will display the details of a specific task. Add the following code to TaskDetails.jsx
:
import React from "react";
import { useParams } from "react-router-dom";
function TaskDetails() {
const { id } = useParams();
return (
<div className="container">
<h1>Task Details</h1>
<p>Details for Task ID: {id}</p>
{/* Fetch and display task details from JSON server here later */}
</div>
);
}
export default TaskDetails;
6. Setup React Router in App.jsx
Update your App.jsx
to include routing using react-router-dom
:
import React from "react";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import Home from "./pages/Home";
import About from "./pages/About";
import TaskDetails from "./pages/TaskDetails";
function App() {
return (
<Router>
<div>
{/* Navigation links */}
<nav>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
</ul>
</nav>
{/* Define Routes */}
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/task/:id" element={<TaskDetails />} />
</Routes>
</div>
</Router>
);
}
export default App;
7. Create Navigation Links
Let’s improve the navigation links using <Link>
from react-router-dom
. Update the App.jsx
navigation section:
import { Link } from "react-router-dom";
<nav>
<ul>
<li><Link to="/">Home</Link></li>
<li><Link to="/about">About</Link></li>
</ul>
</nav>
8. Style the App
Add some basic styles in index.css
:
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
background-color: #f7f8fc;
}
.container {
max-width: 800px;
margin: 2rem auto;
padding: 1rem;
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
nav ul {
list-style: none;
padding: 0;
display: flex;
gap: 1rem;
}
nav ul li {
margin: 0;
}
nav ul li a {
text-decoration: none;
color: #333;
}
nav ul li a:hover {
text-decoration: underline;
}
9. Run the Application
Start the development server:
npm run dev
Navigate to:
http://localhost:5173/
for the Home page.http://localhost:5173/about
for the About page.http://localhost:5173/task/1
to test the TaskDetails page.
Step 3: Creating Components and Fetching Tasks with Axios
In this step, we will:
- Create Header, TaskList, TaskItem, and AddTask components.
- Fetch tasks from the JSON Server using Axios.
- Render tasks in the TaskList component.
1. Create the Header
Component
The Header component will display the app’s title and navigation links.
- Create a new file:
src/components/Header.jsx
- Add the following code:
import React from "react";
import { Link } from "react-router-dom";
function Header() {
return (
<header className="header">
<h1>TODO LIST</h1>
<nav>
<ul>
<li><Link to="/">Home</Link></li>
<li><Link to="/about">About</Link></li>
</ul>
</nav>
</header>
);
}
export default Header;
Update styles in index.css
to style the header:
.header {
text-align: center;
padding: 1rem;
background-color: #6c63ff;
color: white;
margin-bottom: 1rem;
}
.header ul {
list-style: none;
display: flex;
justify-content: center;
gap: 1rem;
padding: 0;
margin: 0;
}
.header ul li a {
color: white;
text-decoration: none;
font-weight: bold;
}
.header ul li a:hover {
text-decoration: underline;
}
2. Create the TaskList
Component
The TaskList component will fetch and display the list of tasks.
- Create a new file:
src/components/TaskList.jsx
- Add the following code:
import React, { useEffect, useState } from "react";
import axios from "axios";
import TaskItem from "./TaskItem";
function TaskList() {
const [tasks, setTasks] = useState([]);
const [loading, setLoading] = useState(true);
// Fetch tasks from the JSON server
useEffect(() => {
const fetchTasks = async () => {
try {
const response = await axios.get("http://localhost:5000/tasks");
setTasks(response.data);
} catch (error) {
console.error("Error fetching tasks:", error);
} finally {
setLoading(false);
}
};
fetchTasks();
}, []);
return (
<div className="task-list">
{loading ? (
<p>Loading tasks...</p>
) : tasks.length > 0 ? (
tasks.map((task) => <TaskItem key={task.id} task={task} />)
) : (
<p>No tasks found.</p>
)}
</div>
);
}
export default TaskList;
Add styles for the task list in index.css
.task-list {
margin-top: 1rem;
}
.task-list p {
text-align: center;
font-style: italic;
}
3. Create the TaskItem
Component
The TaskItem component will render individual task items.
- Create a new file:
src/components/TaskItem.jsx
- Add the following code:
import React from "react";
function TaskItem({ task }) {
return (
<div className={`task-item ${task.completed ? "completed" : ""}`}>
<input type="checkbox" checked={task.completed} readOnly />
<span>{task.title}</span>
<small>{new Date(task.date).toLocaleString()}</small>
</div>
);
}
export default TaskItem;
Add styles for task items in index.css
:
.task-item {
display: flex;
justify-content: space-between;
align-items: center;
background-color: #f9f9f9;
padding: 0.5rem 1rem;
margin-bottom: 0.5rem;
border-radius: 5px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
}
.task-item.completed span {
text-decoration: line-through;
color: #999;
}
.task-item input[type="checkbox"] {
margin-right: 0.5rem;
}
.task-item small {
color: #666;
}
4. Create the AddTask
Component
The AddTask component will allow users to add new tasks.
- Create a new file:
src/components/AddTask.jsx
- Add the following code:
import React, { useState } from "react";
import axios from "axios";
function AddTask({ onTaskAdded }) {
const [taskTitle, setTaskTitle] = useState("");
const handleAddTask = async () => {
if (!taskTitle.trim()) return;
const newTask = {
title: taskTitle,
completed: false,
date: new Date().toISOString(),
};
try {
const response = await axios.post("http://localhost:5000/tasks", newTask);
onTaskAdded(response.data);
setTaskTitle("");
} catch (error) {
console.error("Error adding task:", error);
}
};
return (
<div className="add-task">
<input
type="text"
placeholder="Add a new task..."
value={taskTitle}
onChange={(e) => setTaskTitle(e.target.value)}
/>
<button onClick={handleAddTask}>Add Task</button>
</div>
);
}
export default AddTask;
Add styles for AddTask in index.css
:
.add-task {
display: flex;
margin-top: 1rem;
gap: 0.5rem;
}
.add-task input {
flex: 1;
padding: 0.5rem;
border: 1px solid #ddd;
border-radius: 5px;
}
.add-task button {
padding: 0.5rem 1rem;
background-color: #6c63ff;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
}
.add-task button:hover {
background-color: #5a53d0;
}
5. Update the Home
Page
Update the Home.jsx
file to include the TaskList and AddTask components:
import React, { useState } from "react";
import AddTask from "../components/AddTask";
import TaskList from "../components/TaskList";
function Home() {
const [tasks, setTasks] = useState([]);
const handleTaskAdded = (newTask) => {
setTasks((prevTasks) => [...prevTasks, newTask]);
};
return (
<div className="container">
<AddTask onTaskAdded={handleTaskAdded} />
<TaskList />
</div>
);
}
export default Home;
6. Test the App
- Start the JSON Server:
npx json-server --watch db.json --port 5000
Start the React app
npm run dev
- Open the app in your browser at
http://localhost:5173/
. - Add a new task using the input field.
- View the task list, which should update automatically.
Step 4: Editing, Deleting, and Filtering Tasks
In this step, we will:
- Add the functionality to edit tasks.
- Add the functionality to delete tasks.
- Implement filtering (All, Completed, Pending) using a dropdown.
1. Add Edit Task Functionality
We will create a modal to edit tasks.
Create the EditTaskModal
Component
- Create a new file:
src/components/EditTaskModal.jsx
- Add the following code:
import React, { useState } from "react";
import axios from "axios";
function EditTaskModal({ task, onClose, onTaskUpdated }) {
const [updatedTitle, setUpdatedTitle] = useState(task.title);
const handleUpdateTask = async () => {
const updatedTask = { ...task, title: updatedTitle };
try {
await axios.put(`http://localhost:5000/tasks/${task.id}`, updatedTask);
onTaskUpdated(updatedTask);
onClose();
} catch (error) {
console.error("Error updating task:", error);
}
};
return (
<div className="modal">
<div className="modal-content">
<h3>Edit Task</h3>
<input
type="text"
value={updatedTitle}
onChange={(e) => setUpdatedTitle(e.target.value)}
/>
<div className="modal-actions">
<button onClick={handleUpdateTask}>Save</button>
<button onClick={onClose}>Cancel</button>
</div>
</div>
</div>
);
}
export default EditTaskModal;
Add Styles for the Modal
Add the following CSS to index.css
:
.modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
}
.modal-content {
background: white;
padding: 2rem;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
text-align: center;
}
.modal-actions {
margin-top: 1rem;
display: flex;
justify-content: space-between;
gap: 1rem;
}
.modal-actions button {
padding: 0.5rem 1rem;
border: none;
border-radius: 5px;
cursor: pointer;
}
.modal-actions button:first-child {
background-color: #6c63ff;
color: white;
}
.modal-actions button:last-child {
background-color: #ccc;
color: black;
}
Update TaskItem
to Include Edit Button
Update the TaskItem.jsx
file:
import React, { useState } from "react";
import EditTaskModal from "./EditTaskModal";
function TaskItem({ task, onTaskUpdated }) {
const [isEditing, setIsEditing] = useState(false);
const handleEditClick = () => {
setIsEditing(true);
};
const handleCloseModal = () => {
setIsEditing(false);
};
return (
<div className={`task-item ${task.completed ? "completed" : ""}`}>
<input type="checkbox" checked={task.completed} readOnly />
<span>{task.title}</span>
<small>{new Date(task.date).toLocaleString()}</small>
<button onClick={handleEditClick}>Edit</button>
{isEditing && (
<EditTaskModal
task={task}
onClose={handleCloseModal}
onTaskUpdated={onTaskUpdated}
/>
)}
</div>
);
}
export default TaskItem;
2. Add Delete Task Functionality
Add a delete button to the TaskItem
component.
Update TaskItem
to Include Delete Button
Update the TaskItem.jsx
file:
function TaskItem({ task, onTaskUpdated, onTaskDeleted }) {
const handleDeleteClick = async () => {
try {
await axios.delete(`http://localhost:5000/tasks/${task.id}`);
onTaskDeleted(task.id);
} catch (error) {
console.error("Error deleting task:", error);
}
};
return (
<div className={`task-item ${task.completed ? "completed" : ""}`}>
<input type="checkbox" checked={task.completed} readOnly />
<span>{task.title}</span>
<small>{new Date(task.date).toLocaleString()}</small>
<button onClick={handleDeleteClick}>Delete</button>
</div>
);
}
Step 4: Editing, Deleting, and Filtering Tasks
In this step, we will:
- Add the functionality to edit tasks.
- Add the functionality to delete tasks.
- Implement filtering (All, Completed, Pending) using a dropdown.
1. Add Edit Task Functionality
We will create a modal to edit tasks.
Create the EditTaskModal
Component
- Create a new file:
src/components/EditTaskModal.jsx
- Add the following code:
import React, { useState } from "react";
import axios from "axios";
function EditTaskModal({ task, onClose, onTaskUpdated }) {
const [updatedTitle, setUpdatedTitle] = useState(task.title);
const handleUpdateTask = async () => {
const updatedTask = { ...task, title: updatedTitle };
try {
await axios.put(`http://localhost:5000/tasks/${task.id}`, updatedTask);
onTaskUpdated(updatedTask);
onClose();
} catch (error) {
console.error("Error updating task:", error);
}
};
return (
<div className="modal">
<div className="modal-content">
<h3>Edit Task</h3>
<input
type="text"
value={updatedTitle}
onChange={(e) => setUpdatedTitle(e.target.value)}
/>
<div className="modal-actions">
<button onClick={handleUpdateTask}>Save</button>
<button onClick={onClose}>Cancel</button>
</div>
</div>
</div>
);
}
export default EditTaskModal;
Add Styles for the Modal
Add the following CSS to index.css
:
.modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
}
.modal-content {
background: white;
padding: 2rem;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
text-align: center;
}
.modal-actions {
margin-top: 1rem;
display: flex;
justify-content: space-between;
gap: 1rem;
}
.modal-actions button {
padding: 0.5rem 1rem;
border: none;
border-radius: 5px;
cursor: pointer;
}
.modal-actions button:first-child {
background-color: #6c63ff;
color: white;
}
.modal-actions button:last-child {
background-color: #ccc;
color: black;
}
Update TaskItem
to Include Edit Button
Update the TaskItem.jsx
file:
import React, { useState } from "react";
import EditTaskModal from "./EditTaskModal";
function TaskItem({ task, onTaskUpdated }) {
const [isEditing, setIsEditing] = useState(false);
const handleEditClick = () => {
setIsEditing(true);
};
const handleCloseModal = () => {
setIsEditing(false);
};
return (
<div className={`task-item ${task.completed ? "completed" : ""}`}>
<input type="checkbox" checked={task.completed} readOnly />
<span>{task.title}</span>
<small>{new Date(task.date).toLocaleString()}</small>
<button onClick={handleEditClick}>Edit</button>
{isEditing && (
<EditTaskModal
task={task}
onClose={handleCloseModal}
onTaskUpdated={onTaskUpdated}
/>
)}
</div>
);
}
export default TaskItem;
2. Add Delete Task Functionality
Add a delete button to the TaskItem
component.
Update TaskItem
to Include Delete Button
Update the TaskItem.jsx
file:
function TaskItem({ task, onTaskUpdated, onTaskDeleted }) {
const handleDeleteClick = async () => {
try {
await axios.delete(`http://localhost:5000/tasks/${task.id}`);
onTaskDeleted(task.id);
} catch (error) {
console.error("Error deleting task:", error);
}
};
return (
<div className={`task-item ${task.completed ? "completed" : ""}`}>
<input type="checkbox" checked={task.completed} readOnly />
<span>{task.title}</span>
<small>{new Date(task.date).toLocaleString()}</small>
<button onClick={handleDeleteClick}>Delete</button>
</div>
);
}
3. Add Task Filtering
Add a dropdown for filtering tasks by their completion status.
Create the TaskFilter
Component
- Create a new file:
src/components/TaskFilter.jsx
- Add the following code:
import React from "react";
function TaskFilter({ filter, setFilter }) {
return (
<div className="task-filter">
<select value={filter} onChange={(e) => setFilter(e.target.value)}>
<option value="all">All</option>
<option value="completed">Completed</option>
<option value="pending">Pending</option>
</select>
</div>
);
}
export default TaskFilter;
Add Styles for the Filter
Add the following CSS to index.css
:
.task-filter {
margin: 1rem 0;
}
.task-filter select {
padding: 0.5rem;
border: 1px solid #ddd;
border-radius: 5px;
}
Update TaskList
to Use the Filter
Update the TaskList.jsx
file:
import React, { useEffect, useState } from "react";
import axios from "axios";
import TaskItem from "./TaskItem";
import TaskFilter from "./TaskFilter";
function TaskList() {
const [tasks, setTasks] = useState([]);
const [filter, setFilter] = useState("all");
useEffect(() => {
const fetchTasks = async () => {
try {
const response = await axios.get("http://localhost:5000/tasks");
setTasks(response.data);
} catch (error) {
console.error("Error fetching tasks:", error);
}
};
fetchTasks();
}, []);
const filteredTasks = tasks.filter((task) => {
if (filter === "completed") return task.completed;
if (filter === "pending") return !task.completed;
return true;
});
return (
<div>
<TaskFilter filter={filter} setFilter={setFilter} />
<div className="task-list">
{filteredTasks.length > 0 ? (
filteredTasks.map((task) => (
<TaskItem
key={task.id}
task={task}
onTaskUpdated={(updatedTask) => {
setTasks((prev) =>
prev.map((t) => (t.id === updatedTask.id ? updatedTask : t))
);
}}
onTaskDeleted={(taskId) =>
setTasks((prev) => prev.filter((t) => t.id !== taskId))
}
/>
))
) : (
<p>No tasks found.</p>
)}
</div>
</div>
);
}
export default TaskList;
4. Test the App
- Start the JSON Server:
npx json-server --watch db.json --port 5000
Start the React app
npm run dev
- Open the app in your browser at
http://localhost:5173/
. - Test the following:
- Edit tasks: Click the edit button, update the task title, and save.
- Delete tasks: Click the delete button to remove a task.
- Filter tasks: Use the dropdown to filter by completed or pending tasks
Step 5: Enhancing the To-Do App with Advanced Features
In this step, we will:
- Add bulk actions (e.g., mark all as completed or delete selected tasks).
- Add animations to improve user experience.
- Integrate local storage to persist the filter selection.
1. Add Bulk Actions
Update TaskList
to Include Bulk Actions
Modify the TaskList.jsx
file to include bulk actions for marking all tasks as completed or deleting selected tasks.
import React, { useEffect, useState } from "react";
import axios from "axios";
import TaskItem from "./TaskItem";
import TaskFilter from "./TaskFilter";
function TaskList() {
const [tasks, setTasks] = useState([]);
const [filter, setFilter] = useState("all");
const [selectedTasks, setSelectedTasks] = useState([]);
// Fetch tasks
useEffect(() => {
const fetchTasks = async () => {
try {
const response = await axios.get("http://localhost:5000/tasks");
setTasks(response.data);
} catch (error) {
console.error("Error fetching tasks:", error);
}
};
fetchTasks();
}, []);
const handleSelectTask = (id) => {
setSelectedTasks((prev) =>
prev.includes(id) ? prev.filter((taskId) => taskId !== id) : [...prev, id]
);
};
const handleMarkAllCompleted = async () => {
try {
const updatedTasks = await Promise.all(
tasks.map((task) => {
if (selectedTasks.includes(task.id) && !task.completed) {
return axios
.put(`http://localhost:5000/tasks/${task.id}`, {
...task,
completed: true,
})
.then((res) => res.data);
}
return task;
})
);
setTasks(updatedTasks);
setSelectedTasks([]);
} catch (error) {
console.error("Error marking tasks as completed:", error);
}
};
const handleDeleteSelected = async () => {
try {
await Promise.all(
selectedTasks.map((id) =>
axios.delete(`http://localhost:5000/tasks/${id}`)
)
);
setTasks((prev) => prev.filter((task) => !selectedTasks.includes(task.id)));
setSelectedTasks([]);
} catch (error) {
console.error("Error deleting tasks:", error);
}
};
const filteredTasks = tasks.filter((task) => {
if (filter === "completed") return task.completed;
if (filter === "pending") return !task.completed;
return true;
});
return (
<div>
<TaskFilter filter={filter} setFilter={setFilter} />
<div className="bulk-actions">
<button onClick={handleMarkAllCompleted} disabled={!selectedTasks.length}>
Mark Selected as Completed
</button>
<button onClick={handleDeleteSelected} disabled={!selectedTasks.length}>
Delete Selected
</button>
</div>
<div className="task-list">
{filteredTasks.length > 0 ? (
filteredTasks.map((task) => (
<TaskItem
key={task.id}
task={task}
isSelected={selectedTasks.includes(task.id)}
onSelect={handleSelectTask}
onTaskUpdated={(updatedTask) => {
setTasks((prev) =>
prev.map((t) => (t.id === updatedTask.id ? updatedTask : t))
);
}}
onTaskDeleted={(taskId) =>
setTasks((prev) => prev.filter((t) => t.id !== taskId))
}
/>
))
) : (
<p>No tasks found.</p>
)}
</div>
</div>
);
}
export default TaskList;
Update TaskItem
to Support Selection
Update TaskItem.jsx
:
function TaskItem({ task, isSelected, onSelect, onTaskUpdated, onTaskDeleted }) {
return (
<div className={`task-item ${task.completed ? "completed" : ""}`}>
<input
type="checkbox"
checked={isSelected}
onChange={() => onSelect(task.id)}
/>
<input
type="checkbox"
checked={task.completed}
readOnly
/>
<span>{task.title}</span>
<small>{new Date(task.date).toLocaleString()}</small>
<button onClick={() => onTaskDeleted(task.id)}>Delete</button>
</div>
);
}
export default TaskItem;
2. Add Animations
To enhance the user experience, we’ll use the react-transition-group
library.
Install react-transition-group
Run the following command to install the library:
npm install react-transition-group
Wrap TaskList with Transitions
Update the TaskList.jsx
file:
import { CSSTransition, TransitionGroup } from "react-transition-group";
import "./TaskList.css"; // Add this file for custom animation styles
function TaskList() {
// ...
return (
<div>
<TaskFilter filter={filter} setFilter={setFilter} />
<TransitionGroup className="task-list">
{filteredTasks.map((task) => (
<CSSTransition key={task.id} timeout={500} classNames="task">
<TaskItem
task={task}
isSelected={selectedTasks.includes(task.id)}
onSelect={handleSelectTask}
onTaskUpdated={(updatedTask) => {
setTasks((prev) =>
prev.map((t) => (t.id === updatedTask.id ? updatedTask : t))
);
}}
onTaskDeleted={(taskId) =>
setTasks((prev) => prev.filter((t) => t.id !== taskId))
}
/>
</CSSTransition>
))}
</TransitionGroup>
</div>
);
}
Add CSS for Animations
Create a new file: src/components/TaskList.css
and add the following:
.task-enter {
opacity: 0;
transform: translateY(-10px);
}
.task-enter-active {
opacity: 1;
transform: translateY(0);
transition: all 500ms;
}
.task-exit {
opacity: 1;
transform: translateY(0);
}
.task-exit-active {
opacity: 0;
transform: translateY(10px);
transition: all 500ms;
}
3. Persist Filter with Local Storage
Update the TaskList.jsx
file to save and retrieve the filter value from local storage:
useEffect(() => {
const savedFilter = localStorage.getItem("taskFilter");
if (savedFilter) setFilter(savedFilter);
}, []);
useEffect(() => {
localStorage.setItem("taskFilter", filter);
}, [filter]);
Step 6: Adding Dark Mode and Deploying the To-Do App
In this step, we will:
- Add a Dark Mode Toggle to switch between light and dark themes.
- Deploy the application using Netlify or Vercel.
1. Adding Dark Mode Toggle
Update the Application to Support Themes
Step 1: Create a ThemeContext
We will use the React Context API to manage the theme state.
- Create a new file:
src/context/ThemeContext.jsx
- Add the following code:
import React, { createContext, useState, useEffect } from "react";
export const ThemeContext = createContext();
export function ThemeProvider({ children }) {
const [theme, setTheme] = useState("light");
useEffect(() => {
const savedTheme = localStorage.getItem("theme") || "light";
setTheme(savedTheme);
document.documentElement.setAttribute("data-theme", savedTheme);
}, []);
const toggleTheme = () => {
const newTheme = theme === "light" ? "dark" : "light";
setTheme(newTheme);
document.documentElement.setAttribute("data-theme", newTheme);
localStorage.setItem("theme", newTheme);
};
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
}
Step 2: Wrap the App with ThemeProvider
Update main.jsx
:
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import "./index.css";
import { ThemeProvider } from "./context/ThemeContext";
ReactDOM.createRoot(document.getElementById("root")).render(
<React.StrictMode>
<ThemeProvider>
<App />
</ThemeProvider>
</React.StrictMode>
);
Step 3: Create the DarkModeToggle
Component
- Create a new file:
src/components/DarkModeToggle.jsx
- Add the following code:
import React, { useContext } from "react";
import { ThemeContext } from "../context/ThemeContext";
function DarkModeToggle() {
const { theme, toggleTheme } = useContext(ThemeContext);
return (
<button onClick={toggleTheme} className="dark-mode-toggle">
{theme === "light" ? "Switch to Dark Mode" : "Switch to Light Mode"}
</button>
);
}
export default DarkModeToggle;
Step 4: Add Dark Mode Styles
Update index.css
to include styles for dark mode:
:root {
--background-color: #ffffff;
--text-color: #333333;
--header-background: #6c63ff;
--button-background: #6c63ff;
--button-text-color: white;
}
[data-theme="dark"] {
--background-color: #121212;
--text-color: #ffffff;
--header-background: #444444;
--button-background: #555555;
--button-text-color: white;
}
body {
background-color: var(--background-color);
color: var(--text-color);
transition: background-color 0.3s, color 0.3s;
}
header {
background-color: var(--header-background);
}
button {
background-color: var(--button-background);
color: var(--button-text-color);
transition: background-color 0.3s, color 0.3s;
}
Step 5: Add the Toggle Button to the Header
Update Header.jsx
:
import React from "react";
import { Link } from "react-router-dom";
import DarkModeToggle from "./DarkModeToggle";
function Header() {
return (
<header className="header">
<h1>TODO LIST</h1>
<nav>
<ul>
<li><Link to="/">Home</Link></li>
<li><Link to="/about">About</Link></li>
</ul>
</nav>
<DarkModeToggle />
</header>
);
}
export default Header;
Step 6: Adding Dark Mode and Deploying the To-Do App
In this step, we will:
- Add a Dark Mode Toggle to switch between light and dark themes.
- Deploy the application using Netlify or Vercel.
1. Adding Dark Mode Toggle
Update the Application to Support Themes
Step 1: Create a ThemeContext
We will use the React Context API to manage the theme state.
- Create a new file:
src/context/ThemeContext.jsx
- Add the following code:
import React, { createContext, useState, useEffect } from "react";
export const ThemeContext = createContext();
export function ThemeProvider({ children }) {
const [theme, setTheme] = useState("light");
useEffect(() => {
const savedTheme = localStorage.getItem("theme") || "light";
setTheme(savedTheme);
document.documentElement.setAttribute("data-theme", savedTheme);
}, []);
const toggleTheme = () => {
const newTheme = theme === "light" ? "dark" : "light";
setTheme(newTheme);
document.documentElement.setAttribute("data-theme", newTheme);
localStorage.setItem("theme", newTheme);
};
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
}
Step 2: Wrap the App with ThemeProvider
Update main.jsx
:
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import "./index.css";
import { ThemeProvider } from "./context/ThemeContext";
ReactDOM.createRoot(document.getElementById("root")).render(
<React.StrictMode>
<ThemeProvider>
<App />
</ThemeProvider>
</React.StrictMode>
);
Step 3: Create the DarkModeToggle
Component
- Create a new file:
src/components/DarkModeToggle.jsx
- Add the following code:
import React, { useContext } from "react";
import { ThemeContext } from "../context/ThemeContext";
function DarkModeToggle() {
const { theme, toggleTheme } = useContext(ThemeContext);
return (
<button onClick={toggleTheme} className="dark-mode-toggle">
{theme === "light" ? "Switch to Dark Mode" : "Switch to Light Mode"}
</button>
);
}
export default DarkModeToggle;
Step 4: Add Dark Mode Styles
Update index.css
to include styles for dark mode:
:root {
--background-color: #ffffff;
--text-color: #333333;
--header-background: #6c63ff;
--button-background: #6c63ff;
--button-text-color: white;
}
[data-theme="dark"] {
--background-color: #121212;
--text-color: #ffffff;
--header-background: #444444;
--button-background: #555555;
--button-text-color: white;
}
body {
background-color: var(--background-color);
color: var(--text-color);
transition: background-color 0.3s, color 0.3s;
}
header {
background-color: var(--header-background);
}
button {
background-color: var(--button-background);
color: var(--button-text-color);
transition: background-color 0.3s, color 0.3s;
}
Step 5: Add the Toggle Button to the Header
Update Header.jsx
:
import React from "react";
import { Link } from "react-router-dom";
import DarkModeToggle from "./DarkModeToggle";
function Header() {
return (
<header className="header">
<h1>TODO LIST</h1>
<nav>
<ul>
<li><Link to="/">Home</Link></li>
<li><Link to="/about">About</Link></li>
</ul>
</nav>
<DarkModeToggle />
</header>
);
}
export default Header;
2. Deploy the Application
Option 1: Deploy with Netlify
Build the App for Production:
npm run build
Upload the Build Directory:
- Go to Netlify.
- Drag and drop the
dist
folder from your project into the Netlify dashboard.
Test the Deployment:
- After deployment, you’ll get a live URL (e.g.,
https://your-app-name.netlify.app
).
- After deployment, you’ll get a live URL (e.g.,
Option 2: Deploy with Vercel
- Install Vercel CLI
npm install -g vercel
Deploy the App:
vercel
Welcome to DevTechTutor.com, your ultimate resource for mastering web development and technology! Whether you're a beginner eager to dive into coding or an experienced developer looking to sharpen your skills, DevTechTutor.com is here to guide you every step of the way. Our mission is to make learning web development accessible, engaging, and effective.