T

TechIdea

Ecosystem

Back to react Projects
Intermediate Level

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.json

Step-by-Step Implementation

1

Load initial tasks from localStorage inside useState initialization.

### Step 1: Project Setup Initialize a React project and establish component folder hierarchy.

react
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

2

Create form input component with controlled value state.

### Step 2: Task State & Local Storage Implement task management logic inside App.jsx.

react
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

3

Implement addTask function appending new task object with unique ID.

### Step 3: Components & Rendering Build TaskInput, TaskList, and TaskItem components.

react
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

4

Implement toggleTask function mapping array to toggle completed status.

### Step 4: Edge Cases Prevent empty task submissions and handle empty list states beautifully.

react
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

5

Implement deleteTask function filtering out target ID.

### Step 4: Edge Cases Prevent empty task submissions and handle empty list states beautifully.

react
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

6

Use useEffect to sync tasks state array to localStorage.

### Step 4: Edge Cases Prevent empty task submissions and handle empty list states beautifully.

react
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

localStorage returns null on first run.

Provide fallback empty array in initial state function.

Form submission reloads the page.

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.

Growth Newsletter

Get practical AI tools, SEO tips, and growth guides weekly.

Join creators, students, and businesses scaling with TechIdea.