Task Manager App using MERN Stack
Last Updated :
29 Feb, 2024
Task Manager is very crucial to manage your tasks. In this article, we are going to develop a task manager application using the MERN stack. This application helps users to manage their tasks efficiently, offering essential features like creating new tasks, editing existing ones, and deleting tasks as needed. We'll walk through the step-by-step process of creating this application.
Output Preview: Let us have a look at how the final output will look like.
Preview of Final OutputPrerequisites:
Approach to create Task Manager App:
- Determine the features and functionalities required for the task manager application, such as task creation, editing, deletion, and viewing tasks.
- Choose and install the required dependencies and requirements which are more suitable for the Project.
- Create the folder structure and components of the project.
- Design and Implement the Frontend of the project.
- Create a Backend Server as well as design and implement the APIs required for the project development.
- Integrate the Backend with the Frontend and test it, either manually or using some testing library.
Steps to Create the Frontend:
Step 1: Set up React frontend and get into it using the command
npx create-react-app client
cd client
Step 2: Install the required dependencies(axios, tailwindcss).
npm install axios
npm install -D tailwindcss
npx tailwindcss init
Step 3: Configure the tailwind.config.js file
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ["./src/**/*.{html,js,jsx}"],
theme: {
extend: {},
},
plugins: [],
}
Step 4: Add the Tailwind directives to your CSS in index.css
@tailwind base;
@tailwind components;
@tailwind utilities;
Step 5: Start Tailwind CLI
npx tailwindcss -i ./src/index.css -o ./src/output.css --watch
Project Structure:
Frontend Folder StructureThe updated dependencies in package.json file will look like:
"dependencies": {
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"axios": "^1.6.7",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4"
},
"devDependencies": {
"tailwindcss": "^3.4.1"
}
Example Code: Create the required files and write the following code.
JavaScript
//src/Components/Filterbar.jsx
import React from 'react';
import { useTaskContext } from '../Context/TaskContext';
function Filterbar() {
const { handleFilterClick } = useTaskContext();
return (
<div className="flex justify-center mt-8">
<button
className="filter-button bg-blue-500
hover:bg-blue-600 text-white font-bold py-2 px-4 rounded-l"
onClick={() => handleFilterClick('all')}
>
All
</button>
<button
className="filter-button bg-blue-500 hover:bg-blue-600
text-white font-bold py-2 px-4"
onClick={() => handleFilterClick('completed')}
>
Completed
</button>
<button
className="filter-button bg-blue-500 hover:bg-blue-600
text-white font-bold py-2 px-4 rounded-r"
onClick={() => handleFilterClick('todo')}
>
To Do
</button>
</div>
);
}
export default Filterbar;
JavaScript
//src/Components/Navbar.jsx
import React, { useState } from 'react';
import AddTaskModal from '../Modals/AddTaskModal';
function Navbar() {
const [isModalOpen, setIsModalOpen] = useState(false);
const openModal = () => {
setIsModalOpen(true);
};
const closeModal = () => {
setIsModalOpen(false);
};
return (
<nav className="bg-gray-800 py-4">
<div className="max-w-7xl mx-auto px-4 flex
justify-between items-center">
<div>
<span className="text-white text-lg
font-bold">Task Manager</span>
</div>
<div>
<button className="bg-blue-500 hover:bg-blue-600
text-white font-bold py-2 px-4 rounded"
onClick={openModal}>
Add
</button>
</div>
</div>
<AddTaskModal isOpen={isModalOpen} closeModal={closeModal} />
</nav>
);
}
export default Navbar;
JavaScript
//src/Components/TaskList.jsx
import React, { useState } from 'react';
import { useTaskContext } from '../Context/TaskContext';
import DeleteModal from '../Modals/DeleteModal';
import EditModal from '../Modals/EditModal';
function TaskList() {
const { filteredTasks, updateTaskStatus } = useTaskContext();
const [taskId, setTaskId] = useState('');
const [taskTitle, setTaskTitle] = useState('');
const [taskDescription, setTaskDescription] = useState('');
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
const [isEditModalOpen, setIsEditModalOpen] = useState(false);
const [openDropdownId, setOpenDropdownId] = useState(null);
const handleDelete = (taskId) => {
setTaskId(taskId);
setIsDeleteModalOpen(true);
setOpenDropdownId(null);
};
const handleEdit = (taskId, taskTitle, taskDescription) => {
setTaskId(taskId);
setTaskTitle(taskTitle);
setTaskDescription(taskDescription);
setIsEditModalOpen(true);
setOpenDropdownId(null);
};
const handleComplete = (taskId) => {
updateTaskStatus(taskId, 'completed');
setOpenDropdownId(null);
};
const toggleDropdown = (taskId) => {
setOpenDropdownId(openDropdownId === taskId ? null : taskId);
};
const isDropdownOpen = (taskId) => {
return openDropdownId === taskId;
};
const getStatusColor = (status) => {
switch (status) {
case 'todo':
return 'bg-yellow-200';
case 'completed':
return 'bg-green-200';
default:
return 'bg-gray-200';
}
};
return (
<div className="my-8 grid gap-4 grid-cols-1 sm:grid-cols-2
md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5">
{filteredTasks.map(task => (
<div key={task._id} className=
{`relative rounded-md shadow-md
${getStatusColor(task.status)}`}>
<div className="p-4">
<div className="flex justify-between items-center mb-2">
<h3 className="text-lg font-semibold">
{task.title}
</h3>
<button onClick={() => toggleDropdown(task._id)}
className="text-gray-500 hover:text-gray-700">
<svg className="w-6 h-6" fill="none"
viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M4 6h16M4 12h16M4 18h16" />
</svg>
</button>
</div>
<p className="text-sm text-gray-600 mb-4">
{task.description}</p>
</div>
{isDropdownOpen(task._id) && (
<div className="absolute top-full right-0 mt-2 w-48
bg-white border rounded-md shadow-md z-10">
<button className="block w-full py-2 px-4
text-left hover:bg-gray-100" onClick={() =>
handleEdit(task._id, task.title,
task.description)}>
Edit</button>
<button className="block w-full py-2 px-4
text-left text-red-600 hover:bg-red-100"
onClick={() => handleDelete(task._id)}>
Delete</button>
{task.status !== 'completed' && (
<button className="block w-full py-2 px-4
text-left hover:bg-gray-100"
onClick={() =>
handleComplete(task._id)}>
Mark as Completed
</button>
)}
</div>
)}
</div>
))}
<DeleteModal isOpen={isDeleteModalOpen}
closeModal={() => setIsDeleteModalOpen(false)}
taskId={taskId} />
<EditModal isOpen={isEditModalOpen}
closeModal={() => setIsEditModalOpen(false)} taskId={taskId}
initialTitle={taskTitle} initialDescription={taskDescription} />
</div>
);
}
export default TaskList;
JavaScript
//src/Context/TaskContext.js
import React, {
createContext,
useContext,
useEffect,
useState
} from "react";
import axios from "axios";
const TaskContext = createContext();
const apiUrl = process.env.REACT_APP_API_URL || "http://localhost:5000";
export const useTaskContext = () => {
return useContext(TaskContext);
};
export const TaskProvider = ({ children }) => {
const [tasks, setTasks] = useState([]);
const [filteredTasks, setFilteredTasks] = useState([]);
const [totalTasks, setTotalTasks] = useState(0);
const [completedTasks, setCompletedTasks] = useState(0);
const [todoTasks, setTodoTasks] = useState(0);
useEffect(() => {
fetchData();
}, [totalTasks]);
const fetchData = async () => {
try {
const response = await axios.get(`${apiUrl}/tasks`);
setTasks(response.data);
setFilteredTasks(response.data);
setTotalTasks(response.data.length);
const completedCount = response.data.filter(
(task) => task.status === "completed"
).length;
setCompletedTasks(completedCount);
setTodoTasks(response.data.length - completedCount);
} catch (err) {
console.error("Error fetching data:", err);
}
};
const handleFilterClick = (status) => {
if (status === "all") {
setFilteredTasks(tasks);
} else {
const filtered = tasks.filter((task) =>
task.status === status);
setFilteredTasks(filtered);
}
};
const addTask = async (title, description, status) => {
try {
const response = await axios.post(`${apiUrl}/tasks`, {
title,
description,
status,
});
setTasks([...tasks, response.data]);
if (status === "completed") {
setCompletedTasks((prev) => prev + 1);
} else {
setTodoTasks((prev) => prev + 1);
}
setTotalTasks((prev) => prev + 1);
} catch (err) {
console.error("Error adding task:", err);
}
};
const deleteTask = async (taskId) => {
try {
await axios.delete(`${apiUrl}/tasks/${taskId}`);
const updatedTasks = tasks.filter((task) => task.id !== taskId);
setTasks(updatedTasks);
setFilteredTasks(updatedTasks);
setTotalTasks((prev) => prev - 1);
const completedCount = updatedTasks.filter(
(task) => task.status === "completed"
).length;
setCompletedTasks(completedCount);
setTodoTasks(updatedTasks.length - completedCount);
} catch (err) {
console.error("Error deleting task:", err);
}
};
const editTask = async (
taskId,
updatedTitle,
updatedDescription,
updatedStatus
) => {
try {
await axios.put(`${apiUrl}/tasks/${taskId}`, {
title: updatedTitle,
description: updatedDescription,
status: updatedStatus,
});
fetchData();
} catch (err) {
console.error("Error editing task:", err);
}
};
const updateTaskStatus = async (taskId, status) => {
try {
await axios.put(`${apiUrl}/tasks/${taskId}`, { status });
const updatedTasks = tasks.map((task) =>
task._id === taskId ? { ...task, status } : task
);
setTasks(updatedTasks);
setFilteredTasks(updatedTasks);
setCompletedTasks((prev) =>
status === "completed" ? prev + 1 : prev - 1
);
setTodoTasks((prev) => (status !== "completed" ?
prev + 1 : prev - 1));
} catch (err) {
console.error("Error updating task status:", err);
}
};
return (
<TaskContext.Provider
value={{
filteredTasks,
totalTasks,
completedTasks,
todoTasks,
handleFilterClick,
addTask,
deleteTask,
editTask,
updateTaskStatus,
}}
>
{children}
</TaskContext.Provider>
);
};
JavaScript
//src/Modals/AddTaskModal.jsx
import React, { useState } from 'react';
import { useTaskContext } from '../Context/TaskContext';
function AddTaskModal({ isOpen, closeModal }) {
const { addTask } = useTaskContext();
const [title, setTitle] = useState('');
const [description, setDescription] = useState('');
const [status, setStatus] = useState('todo');
const handleSubmit = () => {
addTask(title, description, status);
setTitle('');
setDescription('');
setStatus('todo');
closeModal();
};
return (
<div className={`modal ${isOpen ? 'block' : 'hidden'}
fixed inset-0 z-10 overflow-y-auto`}
style={{ backgroundColor: 'rgba(0, 0, 0, 0.5)' }}>
<div className="modal-container bg-white
w-full md:w-1/3 mx-auto mt-20 p-6 rounded shadow-lg">
<div className="modal-header flex justify-between items-center">
<h3 className="text-lg font-semibold">Add New Task</h3>
<button className="text-gray-500 hover:text-gray-800"
onClick={closeModal}>X</button>
</div>
<div className="modal-body mt-4">
<div className="mb-4">
<label className="block text-gray-700 text-sm
font-bold mb-2" htmlFor="title">Title</label>
<input className="border rounded w-full py-2
px-3 text-gray-700
leading-tight focus:outline-none
focus:shadow-outline"
id="title" type="text" value={title}
onChange={(e) => setTitle(e.target.value)} />
</div>
<div className="mb-4">
<label className="block text-gray-700 text-sm
font-bold mb-2"
htmlFor="description">
Description
</label>
<input className="border rounded w-full
py-2 px-3 text-gray-700
leading-tight focus:outline-none
focus:shadow-outline"
id="description"
type="text"
value={description}
onChange={(e) => setDescription(e.target.value)}/>
</div>
<button className="bg-blue-500 hover:bg-blue-600
text-white font-bold
py-2 px-4 rounded"
onClick={handleSubmit}>
Add Task
</button>
</div>
</div>
</div>
);
}
export default AddTaskModal;
JavaScript
//src/Modals/DeleteModal.jsx
import React from 'react';
import { useTaskContext } from '../Context/TaskContext';
function DeleteModal({ isOpen, closeModal, taskId }) {
const { deleteTask } = useTaskContext();
const handleDelete = () => {
deleteTask(taskId);
closeModal();
};
return (
<div className={`modal ${isOpen ? 'block' : 'hidden'}
fixed inset-0 z-10 overflow-y-auto`}
style={{ backgroundColor: 'rgba(0, 0, 0, 0.5)' }}>
<div className="modal-container bg-white w-full
md:w-1/3 mx-auto mt-20 p-6 rounded shadow-lg">
<div className="modal-header flex justify-between
items-center">
<h3 className="text-lg font-semibold">Confirm Delete</h3>
<button className="text-gray-500 hover:text-gray-800"
onClick={closeModal}>X</button>
</div>
<div className="modal-body mt-4">
<p>Are you sure you want to delete this task?</p>
<div className="flex justify-end mt-4">
<button className="bg-red-500 hover:bg-red-600
text-white font-bold py-2 px-4 rounded mr-2"
onClick={handleDelete}>Delete</button>
<button className="bg-gray-300 hover:bg-gray-400
text-gray-800 font-bold py-2 px-4 rounded"
onClick={closeModal}>Cancel</button>
</div>
</div>
</div>
</div>
);
}
export default DeleteModal;
JavaScript
//src/Modals/EditModal.jsx
import React, { useState } from 'react';
import { useTaskContext } from '../Context/TaskContext';
function EditModal({ isOpen, closeModal, taskId, initialTitle = '' }) {
const { editTask } = useTaskContext();
const [title, setTitle] = useState(initialTitle);
const handleSubmit = () => {
editTask(taskId, title);
closeModal();
};
return (
<div className={`modal ${isOpen ? 'block' : 'hidden'}
fixed inset-0 z-10 overflow-y-auto`}
style={{ backgroundColor: 'rgba(0, 0, 0, 0.5)' }}>
<div className="modal-container bg-white w-full
md:w-1/3 mx-auto mt-20 p-6 rounded shadow-lg">
<div className="modal-header flex justify-between items-center">
<h3 className="text-lg font-semibold">Edit Task</h3>
<button className="text-gray-500 hover:text-gray-800"
onClick={closeModal}>X</button>
</div>
<div className="modal-body mt-4">
<div className="mb-4">
<label className="block text-gray-700
text-sm font-bold mb-2" htmlFor="title">Title</label>
<input className="border rounded w-full py-2
px-3 text-gray-700
leading-tight focus:outline-none
focus:shadow-outline"
id="title"
type="text"
value={title}
onChange={(e) => setTitle(e.target.value)} />
</div>
<div className="flex justify-end mt-4">
<button className="bg-blue-500 hover:bg-blue-600
text-white font-bold py-2 px-4 rounded mr-2"
onClick={handleSubmit}>Save</button>
<button className="bg-gray-300 hover:bg-gray-400
text-gray-800 font-bold
py-2 px-4 rounded"
onClick={closeModal}>Cancel</button>
</div>
</div>
</div>
</div>
);
}
export default EditModal;
JavaScript
//src/App.js
import './App.css';
import Filterbar from './Components/Filterbar';
import Navbar from './Components/Navbar';
import { TaskProvider } from './Context/TaskContext';
import Tasks from "./Components/TaskList"
function App() {
return (
<>
<TaskProvider>
<Navbar />
<Filterbar />
<Tasks />
</TaskProvider>
</>
);
}
export default App;