Lesson 28 of 28Article24 min
Final Project — Course Progress Tracker
Build a Course Progress Tracker for Rishtaara lessons. Mark lessons complete, filter by status, and see progress percentage. Uses useState, useEffect, useMemo, and localStorage.
Project overview
Build a Course Progress Tracker for Rishtaara lessons. Mark lessons complete, filter by status, and see progress percentage. Uses useState, useEffect, useMemo, and localStorage.
Copy the full App.jsx below into a Vite React project. Run npm run dev and tick lessons as you finish this course.
Real-life example: This app is your personal report card — each checkbox is a lesson mastered, like stars on a language-learning app.
- Track 28 React lessons with checkboxes
- Filter: All / Done / Todo
- Progress bar with percentage
- Saves to localStorage — survives page refresh
Complete App.jsx — copy and run
Extend the project: add React Router pages per lesson, or fetch progress from an API. Deploy with npm run build on Vercel or Netlify.
App.jsx — Course Progress Tracker
import { useState, useEffect, useMemo } from "react";
import "./App.css";
const LESSONS = [
{ id: 1, title: "React Home & Intro", slug: "react-home-intro" },
{ id: 2, title: "Render & createRoot", slug: "react-render-upgrade" },
{ id: 3, title: "ES6 for React", slug: "react-es6" },
{ id: 4, title: "JSX", slug: "react-jsx" },
{ id: 5, title: "Function Components", slug: "react-components" },
{ id: 6, title: "Class Components", slug: "react-class" },
{ id: 7, title: "Props & Children", slug: "react-props" },
{ id: 8, title: "Events & Conditionals", slug: "react-events-conditionals" },
{ id: 9, title: "Lists & Keys", slug: "react-lists" },
{ id: 10, title: "Forms", slug: "react-forms" },
{ id: 11, title: "Styling", slug: "react-styling" },
{ id: 12, title: "React Router", slug: "react-router" },
{ id: 13, title: "Advanced UI", slug: "react-advanced-ui" },
{ id: 14, title: "Practice Path", slug: "react-practice-path" },
{ id: 15, title: "Hooks Intro", slug: "hooks-intro" },
{ id: 16, title: "useState", slug: "hook-usestate" },
{ id: 17, title: "useEffect", slug: "hook-useeffect" },
{ id: 18, title: "useContext", slug: "hook-usecontext" },
{ id: 19, title: "useRef", slug: "hook-useref" },
{ id: 20, title: "useReducer", slug: "hook-usereducer" },
{ id: 21, title: "useCallback", slug: "hook-usecallback" },
{ id: 22, title: "useMemo", slug: "hook-usememo" },
{ id: 23, title: "Custom Hooks", slug: "hook-custom" },
{ id: 24, title: "React Compiler", slug: "react-compiler-note" },
{ id: 25, title: "Patterns Review", slug: "react-patterns-review" },
{ id: 26, title: "Server Components", slug: "react-server-components-note" },
{ id: 27, title: "Interview Prep", slug: "react-interview-prep" },
{ id: 28, title: "This Project!", slug: "react-project" },
];
const STORAGE_KEY = "knowvora-react-progress";
function App() {
const [done, setDone] = useState([]);
const [filter, setFilter] = useState("all");
useEffect(() => {
const saved = localStorage.getItem(STORAGE_KEY);
if (saved) setDone(JSON.parse(saved));
}, []);
useEffect(() => {
localStorage.setItem(STORAGE_KEY, JSON.stringify(done));
}, [done]);
function toggleLesson(id) {
setDone((prev) =>
prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id]
);
}
const filtered = useMemo(() => {
if (filter === "done") return LESSONS.filter((l) => done.includes(l.id));
if (filter === "todo") return LESSONS.filter((l) => !done.includes(l.id));
return LESSONS;
}, [filter, done]);
const percent = Math.round((done.length / LESSONS.length) * 100);
return (
<div className="app">
<header className="header">
<h1>Rishtaara React Progress</h1>
<p>Track your react-complete-guide lessons</p>
</header>
<section className="stats">
<div className="bar-wrap">
<div className="bar" style={{ width: `${percent}%` }} />
</div>
<p>
{done.length} / {LESSONS.length} complete ({percent}%)
</p>
</section>
<div className="filters">
{["all", "done", "todo"].map((f) => (
<button
key={f}
className={filter === f ? "active" : ""}
onClick={() => setFilter(f)}
>
{f.charAt(0).toUpperCase() + f.slice(1)}
</button>
))}
</div>
<ul className="lesson-list">
{filtered.map((lesson) => {
const isDone = done.includes(lesson.id);
return (
<li key={lesson.id} className={isDone ? "done" : ""}>
<label>
<input
type="checkbox"
checked={isDone}
onChange={() => toggleLesson(lesson.id)}
/>
<span>
{lesson.id}. {lesson.title}
</span>
<code>{lesson.slug}</code>
</label>
</li>
);
})}
</ul>
<footer className="footer">
<p>
Practice more: <a href="/mcq/react-mcq">MCQ</a> ·{" "}
<a href="/interview/react-interview">Interview</a>
</p>
</footer>
</div>
);
}
export default App;App.css — optional styling
.app {
max-width: 640px;
margin: 2rem auto;
padding: 0 1rem;
font-family: system-ui, sans-serif;
}
.header h1 {
color: #0891b2;
margin-bottom: 0.25rem;
}
.stats {
margin: 1.5rem 0;
}
.bar-wrap {
height: 12px;
background: #e0f2fe;
border-radius: 999px;
overflow: hidden;
}
.bar {
height: 100%;
background: #0891b2;
transition: width 0.3s ease;
}
.filters {
display: flex;
gap: 0.5rem;
margin-bottom: 1rem;
}
.filters button {
padding: 0.4rem 0.8rem;
border: 1px solid #cbd5e1;
border-radius: 8px;
background: white;
cursor: pointer;
}
.filters button.active {
background: #0891b2;
color: white;
border-color: #0891b2;
}
.lesson-list {
list-style: none;
padding: 0;
}
.lesson-list li {
padding: 0.75rem;
border: 1px solid #e2e8f0;
border-radius: 8px;
margin-bottom: 0.5rem;
}
.lesson-list li.done {
background: #f0fdfa;
opacity: 0.85;
}
.lesson-list label {
display: flex;
align-items: center;
gap: 0.75rem;
cursor: pointer;
}
.lesson-list code {
margin-left: auto;
font-size: 0.75rem;
color: #64748b;
}
.footer {
margin-top: 2rem;
text-align: center;
color: #64748b;
}
.footer a {
color: #0891b2;
}