Thuta Learning
ProjectsWeb Developmentintermediate

Project: Task Tracker Polish

Relax. We'll talk through this in plain words — no textbook voice.

What you'll walk away with

  • Apply what Project: Task Tracker Polish teaches in a real project
  • Write the code yourself and run it
  • Build out an entire project step by step

Let's think about this for a second

In this final part, we'll polish the app so it goes from a "toy demo" to something you could actually use. We need useEffect to save/load data to and from localStorage so tasks don't disappear even when you refresh the browser. Rather than writing this logic directly inside the component, extracting it into a custom hook (useLocalStorage) keeps the code cleaner and reusable. We'll also add "All / Active / Done" filter buttons, and use useMemo to optimize the filtered list so it doesn't get needlessly recomputed as the task list grows. Once this part is done, the entire Task Tracker project is complete.

Let's build it for real

Build a custom hook at src/hooks/useLocalStorage.js — combine useState and useEffect so it accepts a key/initialValue and calls localStorage.setItem whenever the value changes. Replace the useState(tasks) in TaskTracker with useLocalStorage("tasks", []). Add a filter state (all/active/done) and render three filter buttons. Wrap filteredTasks in useMemo so it only recomputes when tasks or filter changes. Finally, add CSS class names to give the app a tidy visual style — flex layout, padding, border-radius, and so on.

Code Example

javascript
// src/hooks/useLocalStorage.js
import { useState, useEffect } from "react";

function useLocalStorage(key, initialValue) {
  const [value, setValue] = useState(() => {
    const saved = localStorage.getItem(key);
    return saved ? JSON.parse(saved) : initialValue;
  });

  useEffect(() => {
    localStorage.setItem(key, JSON.stringify(value));
  }, [key, value]);

  return [value, setValue];
}

export default useLocalStorage;

// src/components/TaskTracker.jsx (excerpt)
import { useState, useMemo } from "react";
import useLocalStorage from "../hooks/useLocalStorage";

function TaskTracker() {
  const [tasks, setTasks] = useLocalStorage("tasks", []);
  const [filter, setFilter] = useState("all");

  const filteredTasks = useMemo(() => {
    if (filter === "active") return tasks.filter((t) => !t.done);
    if (filter === "done") return tasks.filter((t) => t.done);
    return tasks;
  }, [tasks, filter]);

  return (
    <div className="task-tracker">
      <h2>My Tasks</h2>
      <div className="filters">
        <button onClick={() => setFilter("all")}>All</button>
        <button onClick={() => setFilter("active")}>Active</button>
        <button onClick={() => setFilter("done")}>Done</button>
      </div>
      {/* form + list from Part 2 stays the same, mapped over filteredTasks */}
    </div>
  );
}
You should see
Even after refreshing the browser, the task list won't disappear, and clicking the filter buttons will instantly narrow the list down to just Active or Done tasks.

Try it in 5 minutes

Add a task count (like '3 tasks left') next to the filter bar — try calculating the number of active tasks with useMemo; this should only take about 5 minutes.

A quick word of caution

Since localStorage can only store strings, you always need to pair it with JSON.stringify/JSON.parse — otherwise you may get an error when reading the saved data back.

Easy traps

  • Leaving key out of the useEffect dependency array, so localStorage stops syncing when the key changes
  • Forgetting to include filter in the useMemo dependency array, so the list doesn't update even when you click the filter buttons

Now Try It Yourself

Add a task count (like '3 tasks left') next to the filter bar — try calculating the number of active tasks with useMemo; this should only take about 5 minutes.

You'll know it worked when: Even after refreshing the browser, the task list won't disappear, and clicking the filter buttons will instantly narrow the list down to just Active or Done tasks.