Task Tracker App In React

Building a Complete Task Tracker with React Hooks

It’s time to bring everything together! In this final tutorial, we’ll complete our Task Tracker App using all the React Hooks we’ve learned: useState, useEffect, useRef, useMemo, useCallback, and a custom useLocalStorage hook. By the end, you’ll have a fully functional, optimized React app you can proudly add to your portfolio.

If you haven’t looked at the previous blogs, we highly recommend you do that. Scroll to the bottom of this blog to check the complete learning path.

So, let’s dive in one more time!

What You’ll Learn in This Post

  • How to delete tasks from state
  • How to toggle tasks as completed
  • How all the hooks (useState, useEffect, useRef, useMemo, useCallback, custom hooks) come together in a real project

Mini Project Step: Final Features

We’ll extend our Task Tracker App by adding delete and complete toggle features.

Updated App.jsx

import React, { useState, useEffect, useRef, useMemo, useCallback } from "react";
import useLocalStorage from "./hooks/useLocalStorage";

function App() {
  const [tasks, setTasks] = useLocalStorage("tasks", []);
  const [newTask, setNewTask] = useState("");
  const [search, setSearch] = useState("");
  const inputRef = useRef(null);

  // Fetch initial tasks only if localStorage is empty
  useEffect(() => {
    if (tasks.length === 0) {
      fetch("https://jsonplaceholder.typicode.com/todos?_limit=5")
        .then((res) => res.json())
        .then((data) => setTasks(data))
        .catch((err) => console.error("Error fetching tasks:", err));
    }
  }, []); // run once

  // Auto-focus input
  useEffect(() => {
    inputRef.current.focus();
  }, []);

  // Add task
  const addTask = useCallback(
    (e) => {
      e.preventDefault();
      if (!newTask.trim()) return;

      const task = { id: Date.now(), title: newTask, completed: false };
      setTasks([...tasks, task]);
      setNewTask("");
      inputRef.current.focus();
    },
    [newTask, tasks, setTasks]
  );

  // Delete task
  const deleteTask = useCallback(
    (id) => {
      setTasks(tasks.filter((task) => task.id !== id));
    },
    [tasks, setTasks]
  );

  // Toggle completion
  const toggleTask = useCallback(
    (id) => {
      setTasks(
        tasks.map((task) =>
          task.id === id ? { ...task, completed: !task.completed } : task
        )
      );
    },
    [tasks, setTasks]
  );

  // Filtered tasks (search)
  const filteredTasks = useMemo(() => {
    return tasks.filter((task) =>
      (task.title || task.text).toLowerCase().includes(search.toLowerCase())
    );
  }, [tasks, search]);

  return (
    <div style={{ padding: "20px" }}>
      <h1>Task Tracker</h1>

      {/* Search bar */}
      <input
        type="text"
        placeholder="Search tasks..."
        value={search}
        onChange={(e) => setSearch(e.target.value)}
        style={{ marginBottom: "10px", display: "block" }}
      />

      {/* Form to add tasks */}
      <form onSubmit={addTask}>
        <input
          ref={inputRef}
          type="text"
          placeholder="Enter new task"
          value={newTask}
          onChange={(e) => setNewTask(e.target.value)}
        />
        <button type="submit">Add Task</button>
      </form>

      {/* Task list */}
      <ul>
        {filteredTasks.map((task) => (
          <li key={task.id} style={{ margin: "8px 0" }}>
            <span
              onClick={() => toggleTask(task.id)}
              style={{
                textDecoration: task.completed ? "line-through" : "none",
                cursor: "pointer",
              }}
            >
              {task.title || task.text}
            </span>
            <button
              onClick={() => deleteTask(task.id)}
              style={{ marginLeft: "10px", color: "red" }}
            >
              Delete
            </button>
          </li>
        ))}
      </ul>
    </div>
  );
}

export default App;

Click a task → toggles between completed/incomplete
Delete button → removes task from the list
Tasks persist in localStorage thanks to our custom hook

Recap: Hooks We Used in This Series

  1. useState → store tasks and form input
  2. useEffect → fetch initial tasks + auto-focus input
  3. useRef → control focus on the input field
  4. Custom Hook (useLocalStorage) → persist tasks in localStorage
  5. useMemo → optimize search filtering
  6. useCallback → prevent unnecessary re-creations of functions

Final App Features

  • Add new tasks
  • Search tasks
  • Toggle tasks as completed
  • Delete tasks
  • Persist tasks across refreshes
  • Optimized with React Hooks

Conclusion

Congratulations! You’ve built a complete Task Tracker App while learning the most important React Hooks step by step.

This series showed you not only how to use hooks but also how to combine them in a real-world project. With these skills, you’re ready to build more complex apps and even create your own reusable hooks.

Learning Path

Post 1: Introduction to React Hooks (Why They Matter + Starter Project) – Read Post-1

Learn what hooks are, why they replaced class components, and set up the starter Task Tracker App.

Post 2: Mastering useState in React – Read Post 2

Manage state in functional components. Add new tasks dynamically with useState.

Post 3: Understanding useEffect (Side Effects + API Calls) – Read Post 3

Fetch tasks from an API, mimic lifecycle methods, and clean up side effects.

Post 4: Using useRef in React (Not Just for DOM Access) – Read Post 4

Control focus on the input field and learn how to store values without re-renders.

Post 5: Creating Custom Hooks (useLocalStorage) – Read Post 5

Extract reusable logic into a custom hook that persists tasks across refreshes.

Post 6: Optimizing Performance with useMemo and useCallbackRead Post 6

Add a search feature and optimize it with memoization to prevent unnecessary re-renders.

Post 7 (Final): Building a Complete Task Tracker with React Hooks – Current Blog

Add delete + toggle complete functionality, and wrap up with a fully functional app.


Discover more from TACETRA

Subscribe to get the latest posts sent to your email.

Let's have a discussion!

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Discover more from TACETRA

Subscribe now to keep reading and get access to the full archive.

Continue reading