R
Rishtaara
React: The Complete Guide
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;
}