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
// 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>
);
}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.