React Task Manager
Create a feature-rich Task Manager (Todo App) in React demonstrating state management with arrays, controlled form inputs, task completion toggling, deletion, and local storage persistence.
The Problem
Create a feature-rich Task Manager (Todo App) in React demonstrating state management with arrays, controlled form inputs, task completion toggling, deletion, and local storage persistence.
Real-World Use Case
Create a feature-rich Task Manager (Todo App) in React demonstrating state management with arrays, controlled form inputs, task completion toggling, deletion, and local storage persistence.
Technology Stack
Understanding of React useState hook
Prerequisite
Familiarity with array methods (map, filter)
Prerequisite
Knowledge of controlled form components
Prerequisite
Architecture & Design
Folder Structure
task_manager/
├── src/
│ ├── components/
│ │ ├── TaskInput.jsx
│ │ ├── TaskList.jsx
│ │ └── TaskItem.jsx
│ ├── App.jsx
│ └── index.css
└── package.jsonStep-by-Step Implementation
Load initial tasks from localStorage inside useState initialization.
### Step 1: Project Setup Initialize a React project and establish component folder hierarchy.
import { useState, useEffect } from 'react';
export default function TaskManager() {
const [tasks, setTasks] = useState(() => {
const saved = localStorage.getItem('techidea_tasks');
return saved ? JSON.parse(saved) : [];
});
const [text, setText] = useState('');
const [filter, setFilter] = useState('all');
useEffect(() => {
localStorage.setItem('techidea_tasks', JSON.stringify(tasks));
}, [tasks]);
const addTask = (e) => {
e.preventDefault();
if (!text.trim()) return;
const newTask = {
id: Date.now().toString(),
title: text.trim(),
completed: false
};
setTasks(prev => [...prev, newTask]);
setText('');
};
const toggleTask = (id) => {
setTasks(prev => prev.map(task =>
task.id === id ? { ...task, completed: !task.completed } : task
));
};
const deleteTask = (id) => {
setTasks(prev => prev.filter(task => task.id !== id));
};
const filteredTasks = tasks.filter(task => {
if (filter === 'completed') return task.completed;
if (filter === 'active') return !task.completed;
return true;
});
return (
<div className="max-w-lg mx-auto my-10 p-6 bg-white rounded-3xl shadow-sm border border-slate-200">
<h1 className="text-xl font-bold text-slate-900 mb-4">React Task Manager</h1>
<form onSubmit={addTask} className="flex gap-2 mb-6">
<input
type="text"
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="Add a new task..."
className="flex-1 p-3 border border-slate-200 rounded-xl text-xs"
/>
<button type="submit" className="px-5 bg-blue-600 hover:bg-blue-700 text-white font-bold rounded-xl text-xs">
Add
</button>
</form>
<div className="flex gap-2 mb-4 text-xs">
<button onClick={() => setFilter('all')} className={`px-3 py-1 rounded-lg ${filter === 'all' ? 'bg-blue-50 text-blue-600 font-bold' : 'text-slate-600'}`}>All</button>
<button onClick={() => setFilter('active')} className={`px-3 py-1 rounded-lg ${filter === 'active' ? 'bg-blue-50 text-blue-600 font-bold' : 'text-slate-600'}`}>Active</button>
<button onClick={() => setFilter('completed')} className={`px-3 py-1 rounded-lg ${filter === 'completed' ? 'bg-blue-50 text-blue-600 font-bold' : 'text-slate-600'}`}>Completed</button>
</div>
<div className="space-y-2">
{filteredTasks.length === 0 ? (
<p className="text-xs text-slate-400 py-6 text-center italic">No tasks found.</p>
) : (
filteredTasks.map(task => (
<div key={task.id} className="flex items-center justify-between p-3 bg-slate-50 rounded-xl border border-slate-100 text-xs">
<div className="flex items-center gap-3">
<input
type="checkbox"
checked={task.completed}
onChange={() => toggleTask(task.id)}
className="w-4 h-4 text-blue-600 rounded border-slate-300"
/>
<span className={`text-slate-800 ${task.completed ? 'line-through text-slate-400' : 'font-medium'}`}>
{task.title}
</span>
</div>
<button onClick={() => deleteTask(task.id)} className="text-red-500 hover:text-red-700 font-semibold">Delete</button>
</div>
))
)}
</div>
</div>
);
}Code Explanation
Implementation step
Create form input component with controlled value state.
### Step 2: Task State & Local Storage Implement task management logic inside App.jsx.
import { useState, useEffect } from 'react';
export default function TaskManager() {
const [tasks, setTasks] = useState(() => {
const saved = localStorage.getItem('techidea_tasks');
return saved ? JSON.parse(saved) : [];
});
const [text, setText] = useState('');
const [filter, setFilter] = useState('all');
useEffect(() => {
localStorage.setItem('techidea_tasks', JSON.stringify(tasks));
}, [tasks]);
const addTask = (e) => {
e.preventDefault();
if (!text.trim()) return;
const newTask = {
id: Date.now().toString(),
title: text.trim(),
completed: false
};
setTasks(prev => [...prev, newTask]);
setText('');
};
const toggleTask = (id) => {
setTasks(prev => prev.map(task =>
task.id === id ? { ...task, completed: !task.completed } : task
));
};
const deleteTask = (id) => {
setTasks(prev => prev.filter(task => task.id !== id));
};
const filteredTasks = tasks.filter(task => {
if (filter === 'completed') return task.completed;
if (filter === 'active') return !task.completed;
return true;
});
return (
<div className="max-w-lg mx-auto my-10 p-6 bg-white rounded-3xl shadow-sm border border-slate-200">
<h1 className="text-xl font-bold text-slate-900 mb-4">React Task Manager</h1>
<form onSubmit={addTask} className="flex gap-2 mb-6">
<input
type="text"
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="Add a new task..."
className="flex-1 p-3 border border-slate-200 rounded-xl text-xs"
/>
<button type="submit" className="px-5 bg-blue-600 hover:bg-blue-700 text-white font-bold rounded-xl text-xs">
Add
</button>
</form>
<div className="flex gap-2 mb-4 text-xs">
<button onClick={() => setFilter('all')} className={`px-3 py-1 rounded-lg ${filter === 'all' ? 'bg-blue-50 text-blue-600 font-bold' : 'text-slate-600'}`}>All</button>
<button onClick={() => setFilter('active')} className={`px-3 py-1 rounded-lg ${filter === 'active' ? 'bg-blue-50 text-blue-600 font-bold' : 'text-slate-600'}`}>Active</button>
<button onClick={() => setFilter('completed')} className={`px-3 py-1 rounded-lg ${filter === 'completed' ? 'bg-blue-50 text-blue-600 font-bold' : 'text-slate-600'}`}>Completed</button>
</div>
<div className="space-y-2">
{filteredTasks.length === 0 ? (
<p className="text-xs text-slate-400 py-6 text-center italic">No tasks found.</p>
) : (
filteredTasks.map(task => (
<div key={task.id} className="flex items-center justify-between p-3 bg-slate-50 rounded-xl border border-slate-100 text-xs">
<div className="flex items-center gap-3">
<input
type="checkbox"
checked={task.completed}
onChange={() => toggleTask(task.id)}
className="w-4 h-4 text-blue-600 rounded border-slate-300"
/>
<span className={`text-slate-800 ${task.completed ? 'line-through text-slate-400' : 'font-medium'}`}>
{task.title}
</span>
</div>
<button onClick={() => deleteTask(task.id)} className="text-red-500 hover:text-red-700 font-semibold">Delete</button>
</div>
))
)}
</div>
</div>
);
}Code Explanation
Implementation step
Implement addTask function appending new task object with unique ID.
### Step 3: Components & Rendering Build TaskInput, TaskList, and TaskItem components.
import { useState, useEffect } from 'react';
export default function TaskManager() {
const [tasks, setTasks] = useState(() => {
const saved = localStorage.getItem('techidea_tasks');
return saved ? JSON.parse(saved) : [];
});
const [text, setText] = useState('');
const [filter, setFilter] = useState('all');
useEffect(() => {
localStorage.setItem('techidea_tasks', JSON.stringify(tasks));
}, [tasks]);
const addTask = (e) => {
e.preventDefault();
if (!text.trim()) return;
const newTask = {
id: Date.now().toString(),
title: text.trim(),
completed: false
};
setTasks(prev => [...prev, newTask]);
setText('');
};
const toggleTask = (id) => {
setTasks(prev => prev.map(task =>
task.id === id ? { ...task, completed: !task.completed } : task
));
};
const deleteTask = (id) => {
setTasks(prev => prev.filter(task => task.id !== id));
};
const filteredTasks = tasks.filter(task => {
if (filter === 'completed') return task.completed;
if (filter === 'active') return !task.completed;
return true;
});
return (
<div className="max-w-lg mx-auto my-10 p-6 bg-white rounded-3xl shadow-sm border border-slate-200">
<h1 className="text-xl font-bold text-slate-900 mb-4">React Task Manager</h1>
<form onSubmit={addTask} className="flex gap-2 mb-6">
<input
type="text"
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="Add a new task..."
className="flex-1 p-3 border border-slate-200 rounded-xl text-xs"
/>
<button type="submit" className="px-5 bg-blue-600 hover:bg-blue-700 text-white font-bold rounded-xl text-xs">
Add
</button>
</form>
<div className="flex gap-2 mb-4 text-xs">
<button onClick={() => setFilter('all')} className={`px-3 py-1 rounded-lg ${filter === 'all' ? 'bg-blue-50 text-blue-600 font-bold' : 'text-slate-600'}`}>All</button>
<button onClick={() => setFilter('active')} className={`px-3 py-1 rounded-lg ${filter === 'active' ? 'bg-blue-50 text-blue-600 font-bold' : 'text-slate-600'}`}>Active</button>
<button onClick={() => setFilter('completed')} className={`px-3 py-1 rounded-lg ${filter === 'completed' ? 'bg-blue-50 text-blue-600 font-bold' : 'text-slate-600'}`}>Completed</button>
</div>
<div className="space-y-2">
{filteredTasks.length === 0 ? (
<p className="text-xs text-slate-400 py-6 text-center italic">No tasks found.</p>
) : (
filteredTasks.map(task => (
<div key={task.id} className="flex items-center justify-between p-3 bg-slate-50 rounded-xl border border-slate-100 text-xs">
<div className="flex items-center gap-3">
<input
type="checkbox"
checked={task.completed}
onChange={() => toggleTask(task.id)}
className="w-4 h-4 text-blue-600 rounded border-slate-300"
/>
<span className={`text-slate-800 ${task.completed ? 'line-through text-slate-400' : 'font-medium'}`}>
{task.title}
</span>
</div>
<button onClick={() => deleteTask(task.id)} className="text-red-500 hover:text-red-700 font-semibold">Delete</button>
</div>
))
)}
</div>
</div>
);
}Code Explanation
Implementation step
Implement toggleTask function mapping array to toggle completed status.
### Step 4: Edge Cases Prevent empty task submissions and handle empty list states beautifully.
import { useState, useEffect } from 'react';
export default function TaskManager() {
const [tasks, setTasks] = useState(() => {
const saved = localStorage.getItem('techidea_tasks');
return saved ? JSON.parse(saved) : [];
});
const [text, setText] = useState('');
const [filter, setFilter] = useState('all');
useEffect(() => {
localStorage.setItem('techidea_tasks', JSON.stringify(tasks));
}, [tasks]);
const addTask = (e) => {
e.preventDefault();
if (!text.trim()) return;
const newTask = {
id: Date.now().toString(),
title: text.trim(),
completed: false
};
setTasks(prev => [...prev, newTask]);
setText('');
};
const toggleTask = (id) => {
setTasks(prev => prev.map(task =>
task.id === id ? { ...task, completed: !task.completed } : task
));
};
const deleteTask = (id) => {
setTasks(prev => prev.filter(task => task.id !== id));
};
const filteredTasks = tasks.filter(task => {
if (filter === 'completed') return task.completed;
if (filter === 'active') return !task.completed;
return true;
});
return (
<div className="max-w-lg mx-auto my-10 p-6 bg-white rounded-3xl shadow-sm border border-slate-200">
<h1 className="text-xl font-bold text-slate-900 mb-4">React Task Manager</h1>
<form onSubmit={addTask} className="flex gap-2 mb-6">
<input
type="text"
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="Add a new task..."
className="flex-1 p-3 border border-slate-200 rounded-xl text-xs"
/>
<button type="submit" className="px-5 bg-blue-600 hover:bg-blue-700 text-white font-bold rounded-xl text-xs">
Add
</button>
</form>
<div className="flex gap-2 mb-4 text-xs">
<button onClick={() => setFilter('all')} className={`px-3 py-1 rounded-lg ${filter === 'all' ? 'bg-blue-50 text-blue-600 font-bold' : 'text-slate-600'}`}>All</button>
<button onClick={() => setFilter('active')} className={`px-3 py-1 rounded-lg ${filter === 'active' ? 'bg-blue-50 text-blue-600 font-bold' : 'text-slate-600'}`}>Active</button>
<button onClick={() => setFilter('completed')} className={`px-3 py-1 rounded-lg ${filter === 'completed' ? 'bg-blue-50 text-blue-600 font-bold' : 'text-slate-600'}`}>Completed</button>
</div>
<div className="space-y-2">
{filteredTasks.length === 0 ? (
<p className="text-xs text-slate-400 py-6 text-center italic">No tasks found.</p>
) : (
filteredTasks.map(task => (
<div key={task.id} className="flex items-center justify-between p-3 bg-slate-50 rounded-xl border border-slate-100 text-xs">
<div className="flex items-center gap-3">
<input
type="checkbox"
checked={task.completed}
onChange={() => toggleTask(task.id)}
className="w-4 h-4 text-blue-600 rounded border-slate-300"
/>
<span className={`text-slate-800 ${task.completed ? 'line-through text-slate-400' : 'font-medium'}`}>
{task.title}
</span>
</div>
<button onClick={() => deleteTask(task.id)} className="text-red-500 hover:text-red-700 font-semibold">Delete</button>
</div>
))
)}
</div>
</div>
);
}Code Explanation
Implementation step
Implement deleteTask function filtering out target ID.
### Step 4: Edge Cases Prevent empty task submissions and handle empty list states beautifully.
import { useState, useEffect } from 'react';
export default function TaskManager() {
const [tasks, setTasks] = useState(() => {
const saved = localStorage.getItem('techidea_tasks');
return saved ? JSON.parse(saved) : [];
});
const [text, setText] = useState('');
const [filter, setFilter] = useState('all');
useEffect(() => {
localStorage.setItem('techidea_tasks', JSON.stringify(tasks));
}, [tasks]);
const addTask = (e) => {
e.preventDefault();
if (!text.trim()) return;
const newTask = {
id: Date.now().toString(),
title: text.trim(),
completed: false
};
setTasks(prev => [...prev, newTask]);
setText('');
};
const toggleTask = (id) => {
setTasks(prev => prev.map(task =>
task.id === id ? { ...task, completed: !task.completed } : task
));
};
const deleteTask = (id) => {
setTasks(prev => prev.filter(task => task.id !== id));
};
const filteredTasks = tasks.filter(task => {
if (filter === 'completed') return task.completed;
if (filter === 'active') return !task.completed;
return true;
});
return (
<div className="max-w-lg mx-auto my-10 p-6 bg-white rounded-3xl shadow-sm border border-slate-200">
<h1 className="text-xl font-bold text-slate-900 mb-4">React Task Manager</h1>
<form onSubmit={addTask} className="flex gap-2 mb-6">
<input
type="text"
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="Add a new task..."
className="flex-1 p-3 border border-slate-200 rounded-xl text-xs"
/>
<button type="submit" className="px-5 bg-blue-600 hover:bg-blue-700 text-white font-bold rounded-xl text-xs">
Add
</button>
</form>
<div className="flex gap-2 mb-4 text-xs">
<button onClick={() => setFilter('all')} className={`px-3 py-1 rounded-lg ${filter === 'all' ? 'bg-blue-50 text-blue-600 font-bold' : 'text-slate-600'}`}>All</button>
<button onClick={() => setFilter('active')} className={`px-3 py-1 rounded-lg ${filter === 'active' ? 'bg-blue-50 text-blue-600 font-bold' : 'text-slate-600'}`}>Active</button>
<button onClick={() => setFilter('completed')} className={`px-3 py-1 rounded-lg ${filter === 'completed' ? 'bg-blue-50 text-blue-600 font-bold' : 'text-slate-600'}`}>Completed</button>
</div>
<div className="space-y-2">
{filteredTasks.length === 0 ? (
<p className="text-xs text-slate-400 py-6 text-center italic">No tasks found.</p>
) : (
filteredTasks.map(task => (
<div key={task.id} className="flex items-center justify-between p-3 bg-slate-50 rounded-xl border border-slate-100 text-xs">
<div className="flex items-center gap-3">
<input
type="checkbox"
checked={task.completed}
onChange={() => toggleTask(task.id)}
className="w-4 h-4 text-blue-600 rounded border-slate-300"
/>
<span className={`text-slate-800 ${task.completed ? 'line-through text-slate-400' : 'font-medium'}`}>
{task.title}
</span>
</div>
<button onClick={() => deleteTask(task.id)} className="text-red-500 hover:text-red-700 font-semibold">Delete</button>
</div>
))
)}
</div>
</div>
);
}Code Explanation
Implementation step
Use useEffect to sync tasks state array to localStorage.
### Step 4: Edge Cases Prevent empty task submissions and handle empty list states beautifully.
import { useState, useEffect } from 'react';
export default function TaskManager() {
const [tasks, setTasks] = useState(() => {
const saved = localStorage.getItem('techidea_tasks');
return saved ? JSON.parse(saved) : [];
});
const [text, setText] = useState('');
const [filter, setFilter] = useState('all');
useEffect(() => {
localStorage.setItem('techidea_tasks', JSON.stringify(tasks));
}, [tasks]);
const addTask = (e) => {
e.preventDefault();
if (!text.trim()) return;
const newTask = {
id: Date.now().toString(),
title: text.trim(),
completed: false
};
setTasks(prev => [...prev, newTask]);
setText('');
};
const toggleTask = (id) => {
setTasks(prev => prev.map(task =>
task.id === id ? { ...task, completed: !task.completed } : task
));
};
const deleteTask = (id) => {
setTasks(prev => prev.filter(task => task.id !== id));
};
const filteredTasks = tasks.filter(task => {
if (filter === 'completed') return task.completed;
if (filter === 'active') return !task.completed;
return true;
});
return (
<div className="max-w-lg mx-auto my-10 p-6 bg-white rounded-3xl shadow-sm border border-slate-200">
<h1 className="text-xl font-bold text-slate-900 mb-4">React Task Manager</h1>
<form onSubmit={addTask} className="flex gap-2 mb-6">
<input
type="text"
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="Add a new task..."
className="flex-1 p-3 border border-slate-200 rounded-xl text-xs"
/>
<button type="submit" className="px-5 bg-blue-600 hover:bg-blue-700 text-white font-bold rounded-xl text-xs">
Add
</button>
</form>
<div className="flex gap-2 mb-4 text-xs">
<button onClick={() => setFilter('all')} className={`px-3 py-1 rounded-lg ${filter === 'all' ? 'bg-blue-50 text-blue-600 font-bold' : 'text-slate-600'}`}>All</button>
<button onClick={() => setFilter('active')} className={`px-3 py-1 rounded-lg ${filter === 'active' ? 'bg-blue-50 text-blue-600 font-bold' : 'text-slate-600'}`}>Active</button>
<button onClick={() => setFilter('completed')} className={`px-3 py-1 rounded-lg ${filter === 'completed' ? 'bg-blue-50 text-blue-600 font-bold' : 'text-slate-600'}`}>Completed</button>
</div>
<div className="space-y-2">
{filteredTasks.length === 0 ? (
<p className="text-xs text-slate-400 py-6 text-center italic">No tasks found.</p>
) : (
filteredTasks.map(task => (
<div key={task.id} className="flex items-center justify-between p-3 bg-slate-50 rounded-xl border border-slate-100 text-xs">
<div className="flex items-center gap-3">
<input
type="checkbox"
checked={task.completed}
onChange={() => toggleTask(task.id)}
className="w-4 h-4 text-blue-600 rounded border-slate-300"
/>
<span className={`text-slate-800 ${task.completed ? 'line-through text-slate-400' : 'font-medium'}`}>
{task.title}
</span>
</div>
<button onClick={() => deleteTask(task.id)} className="text-red-500 hover:text-red-700 font-semibold">Delete</button>
</div>
))
)}
</div>
</div>
);
}Code Explanation
Implementation step
Common Errors
Provide fallback empty array in initial state function.
Call e.preventDefault() inside form handler.
Security & Performance
Type task name and press Enter to confirm addition.
Click checkbox to verify strikethrough completed styling.
Test filtering tabs (All, Active, Completed).
Refresh browser and verify tasks persist from localStorage correctly.
Add task editing capabilities.
Add drag-and-drop reordering.
Implement due date selection.
Interview Questions
Q: Why pass a function to useState (() => {...})?
A: This is lazy state initialization. It ensures localStorage parsing only executes once during initial component mount.