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
useState→ store tasks and form inputuseEffect→ fetch initial tasks + auto-focus inputuseRef→ control focus on the input field- Custom Hook (
useLocalStorage) → persist tasks in localStorage useMemo→ optimize search filteringuseCallback→ 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 useCallback – Read 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.