In this hands-on lab, you will build a comprehensive task management application that demonstrates state management using React's useState hook. You'll learn how to handle form inputs, manage component state, implement controlled components, and handle various user interactions. The application will allow users to add, edit, complete, and delete tasks with proper form validation and different task states.
Total Estimated Time: 45 minutes
Estimated Time: 8 minutes
In this exercise, you will create a new React project for the task manager and set up the initial state structure using the useState hook. You'll learn how to initialize state with complex data structures and understand the fundamentals of state management in React.
- Open VS Code on your Windows virtual machine
- Open the integrated terminal (`Ctrl + ``)
- Navigate to your desired project directory:
cd Desktop - Create a new React application called "task-manager":
npx create-react-app task-manager
- Navigate into the project directory:
cd task-manager - Open the project in VS Code (File > Open Folder and select
task-manager)
- Start the development server:
npm start
- Verify the application loads in your browser at
http://localhost:3000
- Replace the contents of
src/App.jswith:This establishes multiple state variables using useState hook, demonstrating different types of state: arrays, objects, strings, and null values.import React, { useState } from 'react'; import './App.css'; function App() { // Initialize state for tasks - each task has an id, text, completed status, priority, and dueDate const [tasks, setTasks] = useState([ { id: 1, text: "Complete React fundamentals course", completed: false, priority: "high", dueDate: "2024-12-25", createdAt: new Date().toISOString() }, { id: 2, text: "Build portfolio website", completed: true, priority: "medium", dueDate: "2024-12-20", createdAt: new Date().toISOString() }, { id: 3, text: "Practice JavaScript algorithms", completed: false, priority: "low", dueDate: "2024-12-30", createdAt: new Date().toISOString() } ]); // State for the new task form const [newTask, setNewTask] = useState({ text: '', priority: 'medium', dueDate: '' }); // State for form validation errors const [errors, setErrors] = useState({}); // State for editing a task const [editingId, setEditingId] = useState(null); const [editText, setEditText] = useState(''); // State for filtering tasks const [filter, setFilter] = useState('all'); // 'all', 'active', 'completed' return ( <div className="App"> <div className="task-manager"> <h1>Task Manager</h1> <p>Manage your daily tasks efficiently</p> {/* We'll add components here in the next exercises */} <div className="task-stats"> <p>Total Tasks: {tasks.length}</p> <p>Completed: {tasks.filter(task => task.completed).length}</p> <p>Pending: {tasks.filter(task => !task.completed).length}</p> </div> </div> </div> ); } export default App;
-
Replace the contents of
src/App.csswith:* { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background-color: #f5f7fa; line-height: 1.6; } .App { min-height: 100vh; padding: 2rem; } .task-manager { max-width: 800px; margin: 0 auto; background: white; border-radius: 12px; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1); overflow: hidden; } .task-manager h1 { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 2rem; margin: 0; text-align: center; font-size: 2.5rem; } .task-manager > p { text-align: center; color: #666; padding: 1rem 2rem 0; } .task-stats { display: flex; justify-content: space-around; padding: 1rem 2rem; background: #f8f9fa; border-bottom: 1px solid #e9ecef; } .task-stats p { color: #495057; font-weight: 600; }
-
Save the files and check your browser to see the initial layout with task statistics
Exercise 1 Summary: You have successfully set up a React project with multiple useState hooks to manage different aspects of application state. You learned how to initialize state with complex data structures including arrays of objects, form state, error state, and filter state. The foundation is now ready for building interactive features.
Estimated Time: 12 minutes
In this exercise, you will create a form component that allows users to add new tasks. You'll learn about controlled components, form validation, and how to update state based on user input. This demonstrates the core concept of binding form inputs to state variables.
- Create a
componentsfolder in thesrcdirectory - Create a new file called
TaskForm.jsin thecomponentsfolder - Add the following code:
This demonstrates controlled components where form inputs are bound to state and updated through onChange handlers.
import React from 'react'; import './TaskForm.css'; function TaskForm({ newTask, setNewTask, onSubmit, errors }) { // Handle input changes - this updates state as user types const handleInputChange = (e) => { const { name, value } = e.target; setNewTask(prev => ({ ...prev, [name]: value })); }; // Handle form submission const handleSubmit = (e) => { e.preventDefault(); // Prevent page refresh onSubmit(); }; // Get today's date for minimum date validation const today = new Date().toISOString().split('T')[0]; return ( <form className="task-form" onSubmit={handleSubmit}> <div className="form-header"> <h3>Add New Task</h3> </div> <div className="form-group"> <label htmlFor="task-text">Task Description *</label> <input type="text" id="task-text" name="text" value={newTask.text} onChange={handleInputChange} placeholder="Enter task description..." className={errors.text ? 'error' : ''} /> {errors.text && <span className="error-message">{errors.text}</span>} </div> <div className="form-row"> <div className="form-group"> <label htmlFor="task-priority">Priority</label> <select id="task-priority" name="priority" value={newTask.priority} onChange={handleInputChange} > <option value="low">Low</option> <option value="medium">Medium</option> <option value="high">High</option> </select> </div> <div className="form-group"> <label htmlFor="task-due-date">Due Date</label> <input type="date" id="task-due-date" name="dueDate" value={newTask.dueDate} onChange={handleInputChange} min={today} className={errors.dueDate ? 'error' : ''} /> {errors.dueDate && <span className="error-message">{errors.dueDate}</span>} </div> </div> <button type="submit" className="submit-btn"> Add Task </button> </form> ); } export default TaskForm;
- Create
TaskForm.cssin thecomponentsfolder:.task-form { padding: 2rem; border-bottom: 1px solid #e9ecef; background: #f8f9fa; } .form-header h3 { color: #495057; margin-bottom: 1.5rem; font-size: 1.3rem; } .form-group { margin-bottom: 1.5rem; } .form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; } .form-group label { display: block; margin-bottom: 0.5rem; color: #495057; font-weight: 600; } .form-group input, .form-group select { width: 100%; padding: 0.8rem; border: 2px solid #e9ecef; border-radius: 6px; font-size: 1rem; transition: border-color 0.3s ease; } .form-group input:focus, .form-group select:focus { outline: none; border-color: #667eea; } .form-group input.error, .form-group select.error { border-color: #e74c3c; } .error-message { color: #e74c3c; font-size: 0.9rem; margin-top: 0.3rem; display: block; } .submit-btn { background: #667eea; color: white; border: none; padding: 0.8rem 2rem; border-radius: 6px; font-size: 1rem; font-weight: 600; cursor: pointer; transition: background-color 0.3s ease; } .submit-btn:hover { background: #5a6fd8; } @media (max-width: 768px) { .form-row { grid-template-columns: 1fr; } }
-
Update
src/App.jsto include the form handling logic:import React, { useState } from 'react'; import TaskForm from './components/TaskForm'; import './App.css'; function App() { // ... (keep existing state from previous exercise) const [tasks, setTasks] = useState([ { id: 1, text: "Complete React fundamentals course", completed: false, priority: "high", dueDate: "2024-12-25", createdAt: new Date().toISOString() }, { id: 2, text: "Build portfolio website", completed: true, priority: "medium", dueDate: "2024-12-20", createdAt: new Date().toISOString() }, { id: 3, text: "Practice JavaScript algorithms", completed: false, priority: "low", dueDate: "2024-12-30", createdAt: new Date().toISOString() } ]); const [newTask, setNewTask] = useState({ text: '', priority: 'medium', dueDate: '' }); const [errors, setErrors] = useState({}); const [editingId, setEditingId] = useState(null); const [editText, setEditText] = useState(''); const [filter, setFilter] = useState('all'); // Form validation function const validateForm = () => { const newErrors = {}; if (!newTask.text.trim()) { newErrors.text = 'Task description is required'; } else if (newTask.text.trim().length < 3) { newErrors.text = 'Task description must be at least 3 characters'; } if (!newTask.dueDate) { newErrors.dueDate = 'Due date is required'; } else { const selectedDate = new Date(newTask.dueDate); const today = new Date(); today.setHours(0, 0, 0, 0); if (selectedDate < today) { newErrors.dueDate = 'Due date cannot be in the past'; } } setErrors(newErrors); return Object.keys(newErrors).length === 0; }; // Handle adding a new task const handleAddTask = () => { if (validateForm()) { const task = { id: Date.now(), // Simple ID generation text: newTask.text.trim(), completed: false, priority: newTask.priority, dueDate: newTask.dueDate, createdAt: new Date().toISOString() }; setTasks(prevTasks => [...prevTasks, task]); // Reset form setNewTask({ text: '', priority: 'medium', dueDate: '' }); // Clear errors setErrors({}); } }; return ( <div className="App"> <div className="task-manager"> <h1>Task Manager</h1> <p>Manage your daily tasks efficiently</p> <TaskForm newTask={newTask} setNewTask={setNewTask} onSubmit={handleAddTask} errors={errors} /> <div className="task-stats"> <p>Total Tasks: {tasks.length}</p> <p>Completed: {tasks.filter(task => task.completed).length}</p> <p>Pending: {tasks.filter(task => !task.completed).length}</p> </div> </div> </div> ); } export default App;
-
Save all files and test the form - try adding tasks with validation
Exercise 2 Summary: You have created a fully functional form with controlled components, form validation, and state updates. You learned how to handle input changes with the spread operator to update specific properties in state objects, implement form validation, and manage form submission. The form demonstrates proper controlled component patterns where React state is the single source of truth.
Estimated Time: 12 minutes
In this exercise, you will create a TaskList component that displays tasks and allows users to mark them as complete, delete them, and edit them inline. You'll learn about different patterns for updating state, handling events, and implementing conditional rendering based on state.
- Create
TaskItem.jsin thecomponentsfolder:import React, { useState } from 'react'; import './TaskItem.css'; function TaskItem({ task, onToggleComplete, onDelete, onEdit, isEditing, onStartEdit, onCancelEdit }) { const [editText, setEditText] = useState(task.text); const handleEditSubmit = (e) => { e.preventDefault(); if (editText.trim() && editText.trim() !== task.text) { onEdit(task.id, editText.trim()); } else { onCancelEdit(); } }; const getPriorityClass = (priority) => { return `priority-${priority}`; }; const isOverdue = () => { if (task.completed) return false; const today = new Date(); const dueDate = new Date(task.dueDate); return dueDate < today; }; const formatDate = (dateString) => { const date = new Date(dateString); return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }); }; if (isEditing) { return ( <div className="task-item editing"> <form onSubmit={handleEditSubmit} className="edit-form"> <input type="text" value={editText} onChange={(e) => setEditText(e.target.value)} className="edit-input" autoFocus /> <div className="edit-actions"> <button type="submit" className="save-btn">Save</button> <button type="button" onClick={onCancelEdit} className="cancel-btn"> Cancel </button> </div> </form> </div> ); } return ( <div className={`task-item ${task.completed ? 'completed' : ''} ${isOverdue() ? 'overdue' : ''}`}> <div className="task-content"> <div className="task-checkbox"> <input type="checkbox" checked={task.completed} onChange={() => onToggleComplete(task.id)} id={`task-${task.id}`} /> <label htmlFor={`task-${task.id}`} className="checkbox-label"></label> </div> <div className="task-details"> <div className="task-text">{task.text}</div> <div className="task-meta"> <span className={`priority-badge ${getPriorityClass(task.priority)}`}> {task.priority.toUpperCase()} </span> <span className="due-date"> Due: {formatDate(task.dueDate)} {isOverdue() && <span className="overdue-indicator"> (OVERDUE)</span>} </span> </div> </div> </div> <div className="task-actions"> <button onClick={() => onStartEdit(task.id)} className="edit-btn" disabled={task.completed} > Edit </button> <button onClick={() => onDelete(task.id)} className="delete-btn" > Delete </button> </div> </div> ); } export default TaskItem;
- Create
TaskItem.css:.task-item { display: flex; justify-content: space-between; align-items: center; padding: 1rem; border-bottom: 1px solid #e9ecef; transition: background-color 0.3s ease; } .task-item:hover { background-color: #f8f9fa; } .task-item.completed { opacity: 0.7; background-color: #f1f8ff; } .task-item.overdue { border-left: 4px solid #e74c3c; background-color: #fef2f2; } .task-item.editing { background-color: #fff3cd; } .task-content { display: flex; align-items: center; flex-grow: 1; gap: 1rem; } .task-checkbox { position: relative; } .task-checkbox input[type="checkbox"] { opacity: 0; position: absolute; } .checkbox-label { display: block; width: 20px; height: 20px; border: 2px solid #6c757d; border-radius: 4px; cursor: pointer; position: relative; transition: all 0.3s ease; } .task-checkbox input[type="checkbox"]:checked + .checkbox-label { background-color: #28a745; border-color: #28a745; } .task-checkbox input[type="checkbox"]:checked + .checkbox-label::after { content: '✓'; position: absolute; top: -2px; left: 3px; color: white; font-weight: bold; font-size: 14px; } .task-details { flex-grow: 1; } .task-text { font-size: 1.1rem; color: #495057; margin-bottom: 0.5rem; } .task-item.completed .task-text { text-decoration: line-through; color: #6c757d; } .task-meta { display: flex; align-items: center; gap: 1rem; font-size: 0.9rem; } .priority-badge { padding: 0.2rem 0.6rem; border-radius: 12px; font-size: 0.8rem; font-weight: bold; } .priority-high { background-color: #dc3545; color: white; } .priority-medium { background-color: #ffc107; color: #000; } .priority-low { background-color: #28a745; color: white; } .due-date { color: #6c757d; } .overdue-indicator { color: #e74c3c; font-weight: bold; } .task-actions { display: flex; gap: 0.5rem; } .edit-btn, .delete-btn, .save-btn, .cancel-btn { padding: 0.4rem 0.8rem; border: none; border-radius: 4px; cursor: pointer; font-size: 0.9rem; transition: background-color 0.3s ease; } .edit-btn { background-color: #007bff; color: white; } .edit-btn:hover:not(:disabled) { background-color: #0056b3; } .edit-btn:disabled { background-color: #6c757d; cursor: not-allowed; } .delete-btn { background-color: #dc3545; color: white; } .delete-btn:hover { background-color: #c82333; } .edit-form { display: flex; align-items: center; gap: 1rem; width: 100%; } .edit-input { flex-grow: 1; padding: 0.5rem; border: 2px solid #007bff; border-radius: 4px; font-size: 1rem; } .edit-actions { display: flex; gap: 0.5rem; } .save-btn { background-color: #28a745; color: white; } .cancel-btn { background-color: #6c757d; color: white; }
- Create
TaskList.jsin thecomponentsfolder:import React from 'react'; import TaskItem from './TaskItem'; import './TaskList.css'; function TaskList({ tasks, onToggleComplete, onDelete, onEdit, editingId, onStartEdit, onCancelEdit, filter }) { // Filter tasks based on current filter const filteredTasks = tasks.filter(task => { switch (filter) { case 'active': return !task.completed; case 'completed': return task.completed; default: return true; } }); // Sort tasks: incomplete first, then by priority, then by due date const sortedTasks = [...filteredTasks].sort((a, b) => { // First, sort by completion status if (a.completed !== b.completed) { return a.completed ? 1 : -1; } // Then by priority const priorityOrder = { high: 3, medium: 2, low: 1 }; if (priorityOrder[a.priority] !== priorityOrder[b.priority]) { return priorityOrder[b.priority] - priorityOrder[a.priority]; } // Finally by due date return new Date(a.dueDate) - new Date(b.dueDate); }); if (sortedTasks.length === 0) { return ( <div className="task-list"> <div className="empty-state"> <h3>No tasks found</h3> <p> {filter === 'all' ? 'Add a task to get started!' : `No ${filter} tasks found.` } </p> </div> </div> ); } return ( <div className="task-list"> <div className="task-list-header"> <h3> {filter === 'all' && 'All Tasks'} {filter === 'active' && 'Active Tasks'} {filter === 'completed' && 'Completed Tasks'} <span className="task-count">({sortedTasks.length})</span> </h3> </div> {sortedTasks.map(task => ( <TaskItem key={task.id} task={task} onToggleComplete={onToggleComplete} onDelete={onDelete} onEdit={onEdit} isEditing={editingId === task.id} onStartEdit={onStartEdit} onCancelEdit={onCancelEdit} /> ))} </div> ); } export default TaskList;
- Create
TaskList.css:.task-list { min-height: 200px; } .task-list-header { padding: 1rem 2rem; background-color: #f8f9fa; border-bottom: 2px solid #e9ecef; } .task-list-header h3 { color: #495057; margin: 0; display: flex; align-items: center; gap: 0.5rem; } .task-count { color: #6c757d; font-weight: normal; font-size: 0.9rem; } .empty-state { text-align: center; padding: 3rem 2rem; color: #6c757d; } .empty-state h3 { margin-bottom: 0.5rem; color: #495057; }
Exercise 3 Summary: You have created a comprehensive task list with individual task items that support multiple state operations. You learned how to update array state immutably, handle complex state changes like toggling completion status, implement inline editing with local component state, and create conditional rendering based on different states. The components demonstrate proper separation of concerns and state management patterns.
Estimated Time: 13 minutes
In this exercise, you will implement task filtering functionality and complete the state management by adding all the task manipulation functions to the main App component. You'll learn about complex state updates, array manipulation methods, and how to coordinate state between multiple components.
- Create
TaskFilter.jsin thecomponentsfolder:import React from 'react'; import './TaskFilter.css'; function TaskFilter({ filter, onFilterChange, taskCounts }) { const filters = [ { key: 'all', label: 'All Tasks', count: taskCounts.all }, { key: 'active', label: 'Active', count: taskCounts.active }, { key: 'completed', label: 'Completed', count: taskCounts.completed } ]; return ( <div className="task-filter"> <div className="filter-buttons"> {filters.map(filterOption => ( <button key={filterOption.key} className={`filter-btn ${filter === filterOption.key ? 'active' : ''}`} onClick={() => onFilterChange(filterOption.key)} > {filterOption.label} <span className="filter-count">({filterOption.count})</span> </button> ))} </div> <div className="bulk-actions"> <button className="clear-completed-btn" onClick={() => onFilterChange('clear-completed')} disabled={taskCounts.completed === 0} > Clear Completed ({taskCounts.completed}) </button> </div> </div> ); } export default TaskFilter;
- Create
TaskFilter.css:.task-filter { display: flex; justify-content: space-between; align-items: center; padding: 1rem 2rem; background-color: white; border-bottom: 1px solid #e9ecef; } .filter-buttons { display: flex; gap: 0.5rem; } .filter-btn { background: transparent; border: 2px solid #e9ecef; color: #495057; padding: 0.5rem 1rem; border-radius: 20px; cursor: pointer; transition: all 0.3s ease; font-size: 0.9rem; display: flex; align-items: center; gap: 0.3rem; } .filter-btn:hover { border-color: #667eea; background-color: #f8f9fa; } .filter-btn.active { background-color: #667eea; border-color: #667eea; color: white; } .filter-count { background: rgba(0, 0, 0, 0.1); padding: 0.1rem 0.4rem; border-radius: 10px; font-size: 0.8rem; } .filter-btn.active .filter-count { background: rgba(255, 255, 255, 0.2); } .clear-completed-btn { background: #dc3545; color: white; border: none; padding: 0.5rem 1rem; border-radius: 4px; cursor: pointer; font-size: 0.9rem; transition: background-color 0.3s ease; } .clear-completed-btn:hover:not(:disabled) { background: #c82333; } .clear-completed-btn:disabled { background: #6c757d; cursor: not-allowed; } @media (max-width: 768px) { .task-filter { flex-direction: column; gap: 1rem; align-items: stretch; } .filter-buttons { justify-content: center; } }
- Update
src/App.jswith the complete functionality:import React, { useState, useEffect } from 'react'; import TaskForm from './components/TaskForm'; import TaskList from './components/TaskList'; import TaskFilter from './components/TaskFilter'; import './App.css'; function App() { // Main tasks state const [tasks, setTasks] = useState([ { id: 1, text: "Complete React fundamentals course", completed: false, priority: "high", dueDate: "2024-12-25", createdAt: new Date().toISOString() }, { id: 2, text: "Build portfolio website", completed: true, priority: "medium", dueDate: "2024-12-20", createdAt: new Date().toISOString() }, { id: 3, text: "Practice JavaScript algorithms", completed: false, priority: "low", dueDate: "2024-12-30", createdAt: new Date().toISOString() } ]); // Form state const [newTask, setNewTask] = useState({ text: '', priority: 'medium', dueDate: '' }); // Validation errors state const [errors, setErrors] = useState({}); // Editing state const [editingId, setEditingId] = useState(null); // Filter state const [filter, setFilter] = useState('all'); // Calculate task counts for filtering const taskCounts = { all: tasks.length, active: tasks.filter(task => !task.completed).length, completed: tasks.filter(task => task.completed).length }; // Form validation const validateForm = () => { const newErrors = {}; if (!newTask.text.trim()) { newErrors.text = 'Task description is required'; } else if (newTask.text.trim().length < 3) { newErrors.text = 'Task description must be at least 3 characters'; } if (!newTask.dueDate) { newErrors.dueDate = 'Due date is required'; } else { const selectedDate = new Date(newTask.dueDate); const today = new Date(); today.setHours(0, 0, 0, 0); if (selectedDate < today) { newErrors.dueDate = 'Due date cannot be in the past'; } } setErrors(newErrors); return Object.keys(newErrors).length === 0; }; // Add new task const handleAddTask = () => { if (validateForm()) { const task = { id: Date.now(), text: newTask.text.trim(), completed: false, priority: newTask.priority, dueDate: newTask.dueDate, createdAt: new Date().toISOString() }; setTasks(prevTasks => [...prevTasks, task]); // Reset form setNewTask({ text: '', priority: 'medium', dueDate: '' }); setErrors({}); } }; // Toggle task completion const handleToggleComplete = (taskId) => { setTasks(prevTasks => prevTasks.map(task => task.id === taskId ? { ...task, completed: !task.completed } : task ) ); }; // Delete task const handleDeleteTask = (taskId) => { setTasks(prevTasks => prevTasks.filter(task => task.id !== taskId)); // If we're editing the task being deleted, cancel editing if (editingId === taskId) { setEditingId(null); } }; // Start editing a task const handleStartEdit = (taskId) => { setEditingId(taskId); }; // Cancel editing const handleCancelEdit = () => { setEditingId(null); }; // Save edited task const handleEditTask = (taskId, newText) => { setTasks(prevTasks => prevTasks.map(task => task.id === taskId ? { ...task, text: newText } : task ) ); setEditingId(null); }; // Handle filter changes const handleFilterChange = (newFilter) => { if (newFilter === 'clear-completed') { setTasks(prevTasks => prevTasks.filter(task => !task.completed)); } else { setFilter(newFilter); } }; // Load tasks from localStorage on component mount useEffect(() => { const savedTasks = localStorage.getItem('tasks'); if (savedTasks) { try { setTasks(JSON.parse(savedTasks)); } catch (error) { console.error('Error loading tasks from localStorage:', error); } } }, []); // Save tasks to localStorage whenever tasks change useEffect(() => { localStorage.setItem('tasks', JSON.stringify(tasks)); }, [tasks]); return ( <div className="App"> <div className="task-manager"> <h1>Task Manager</h1> <p>Manage your daily tasks efficiently</p> <TaskForm newTask={newTask} setNewTask={setNewTask} onSubmit={handleAddTask} errors={errors} /> <TaskFilter filter={filter} onFilterChange={handleFilterChange} taskCounts={taskCounts} /> <TaskList tasks={tasks} onToggleComplete={handleToggleComplete} onDelete={handleDeleteTask} onEdit={handleEditTask} editingId={editingId} onStartEdit={handleStartEdit} onCancelEdit={handleCancelEdit} filter={filter} /> <div className="task-stats"> <div className="stat-item"> <span className="stat-number">{taskCounts.all}</span> <span className="stat-label">Total Tasks</span> </div> <div className="stat-item"> <span className="stat-number">{taskCounts.completed}</span> <span className="stat-label">Completed</span> </div> <div className="stat-item"> <span className="stat-number">{taskCounts.active}</span> <span className="stat-label">Active</span> </div> </div> </div> </div> ); } export default App;
- Update
src/App.css:* { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background-color: #f5f7fa; line-height: 1.6; } .App { min-height: 100vh; padding: 2rem; } .task-manager { max-width: 800px; margin: 0 auto; background: white; border-radius: 12px; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1); overflow: hidden; } .task-manager h1 { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 2rem; margin: 0; text-align: center; font-size: 2.5rem; } .task-manager > p { text-align: center; color: #666; padding: 1rem 2rem 0; } .task-stats { display: flex; justify-content: space-around; padding: 1.5rem 2rem; background: #f8f9fa; border-top: 1px solid #e9ecef; } .stat-item { text-align: center; display: flex; flex-direction: column; gap: 0.3rem; } .stat-number { font-size: 1.8rem; font-weight: bold; color: #667eea; } .stat-label { color: #6c757d; font-size: 0.9rem; text-transform: uppercase; letter-spacing: 0.5px; } @media (max-width: 768px) { .App { padding: 1rem; } .task-manager h1 { font-size: 2rem; padding: 1.5rem; } .task-stats { padding: 1rem; } .stat-number { font-size: 1.5rem; } }
- Save all files and test the complete task manager:
- Add new tasks with validation
- Mark tasks as complete/incomplete
- Edit task descriptions inline
- Delete tasks
- Filter tasks by status
- Clear completed tasks
- Notice that tasks persist when you refresh the page
Exercise 4 Summary: You have completed a full-featured task management application with comprehensive state management. You learned how to handle complex state updates using the spread operator and array methods, implement localStorage for data persistence using useEffect, coordinate state between multiple components, and manage different types of user interactions including CRUD operations (Create, Read, Update, Delete).
Congratulations! You have successfully completed the Interactive Task Manager lab. In this comprehensive lab, you accomplished:
✅ Mastered useState Hook: Implemented state management for forms, lists, editing, and filtering
✅ Created Controlled Components: Built form inputs that are fully controlled by React state
✅ Implemented Form Validation: Added real-time validation with error handling and user feedback
✅ Handled Complex State Updates: Used spread operator and array methods for immutable updates
✅ Built Interactive User Interface: Created edit-in-place functionality and dynamic filtering
✅ Added Data Persistence: Used useEffect and localStorage to persist data across sessions
✅ Implemented CRUD Operations: Full Create, Read, Update, Delete functionality for tasks
State Management Fundamentals:
- useState Hook: Managing different types of state (strings, objects, arrays, booleans)
- State Updates: Immutable updates using spread operator and array methods
- Complex State: Managing related pieces of state that work together
Controlled Components:
- Form Binding: Connecting form inputs to state variables
- Event Handling: onChange and onSubmit event handlers
- Two-Way Data Binding: State drives UI, UI updates state
Component Communication:
- Props vs State: Understanding when to use each
- Callback Props: Passing functions to child components for state updates
- State Lifting: Managing shared state in parent components
Side Effects:
- useEffect Hook: Running code in response to state changes
- Data Persistence: Saving and loading data from localStorage
- Component Lifecycle: Understanding when effects run
State Update Patterns:
- Array manipulation (add, remove, update items)
- Object property updates using spread operator
- Conditional state updates based on user actions
- Bulk operations (clear completed tasks)
Form Handling:
- Multi-field form state management
- Real-time validation and error display
- Form reset after successful submission
- Date validation and constraints
User Experience Features:
- Inline editing with save/cancel functionality
- Visual feedback for task states (completed, overdue)
- Dynamic filtering and counting
- Responsive design for mobile devices
- Task Management Systems: Understanding common patterns in productivity apps
- Form Validation: Implementing robust client-side validation
- Data Persistence: Working with browser storage APIs
- Interactive UI: Creating responsive, user-friendly interfaces
- State Architecture: Organizing state in scalable ways
- Error Handling: Graceful handling of user errors and edge cases
Your task manager demonstrates solid understanding of React state management. In the next lesson, you'll learn about the useEffect hook in more detail, including data fetching from APIs, handling side effects, and managing component lifecycles - building on the state management foundation you've established here.
Time Completed: ~45 minutes
If you finish early, try these additional features:
- Add task categories/tags with filtering by category
- Implement drag-and-drop reordering of tasks
- Add due date notifications for upcoming deadlines
- Create task search functionality to find specific tasks
- Add bulk actions like "mark all as complete"
- Implement task archiving instead of deletion
- Add task priority sorting and visual indicators