CodeBlanks Logo Icon CodeBlanks

Add via modal

Write an add modal with portal and Suspense.

Project outcome

In this project we built a dashboard: layout and routing skeleton, a stats widget, and an add modal. Each piece was added in one lesson, and the previous lesson's solution became the next one's starting code.

You learned nested routes with React Router, child routes via <Outlet>, widgets with StatCard, and form addition with AddStatModal.

At the end you have a modular, real-world app skeleton. In the next level we will grow this structure with context and data fetching.

Final step: combine

Now you combine all pieces in a single file. You write StatCard and AddStatModal definitions together with StatsPage and DashboardLayout components.

StatsPage holds two states: a stats array and an isOpen boolean. The add button opens the modal; the modal's onAdd adds a new StatCard.

This pattern is common in real projects: small pieces are separate components; the main page combines them.

Practice Dashboard final

Combine all pieces: write StatCard, AddStatModal, StatsPage and DashboardLayout. In StatsPage keep a stats array and isOpen in useState; an "Add" button calls setIsOpen(true). The onAdd from AddStatModal adds a new StatCard. DashboardApp shows StatsPage on /stats with BrowserRouter. export default DashboardApp.

Starter code

App.jsx
import { useState, useRef } from "react";
import { BrowserRouter, Routes, Route, NavLink, Outlet } from "react-router-dom";

// StatCard, AddStatModal, StatsPage, DashboardLayout, DashboardApp buraya yaz

Sample solution

App.jsx
import { useState, useRef } from "react";
import { BrowserRouter, Routes, Route, NavLink, Outlet } from "react-router-dom";

function StatCard({ label, value }) {
  return (
    <article>
      <h3>{label}</h3>
      <p>{value}</p>
    </article>
  );
}

function AddStatModal({ isOpen, onAdd, onClose }) {
  const inputRef = useRef(null);
  if (!isOpen) return null;
  function handleSubmit(e) {
    e.preventDefault();
    onAdd(inputRef.current.value);
    onClose();
  }
  return (
    <div>
      <form onSubmit={handleSubmit}>
        <input ref={inputRef} />
        <button type="submit">Add</button>
      </form>
    </div>
  );
}

function StatsPage() {
  const [stats, setStats] = useState([{ label: "Users", value: 120 }]);
  const [isOpen, setIsOpen] = useState(false);
  return (
    <section>
      {stats.map((s, i) => (
        <StatCard key={i} label={s.label} value={s.value} />
      ))}
      <button onClick={() => setIsOpen(true)}>Add</button>
      <AddStatModal
        isOpen={isOpen}
        onAdd={(v) => setStats([...stats, { label: v, value: 0 }])}
        onClose={() => setIsOpen(false)}
      />
    </section>
  );
}

function DashboardLayout() {
  return (
    <>
      <nav><NavLink to="/stats">Stats</NavLink></nav>
      <main><Outlet /></main>
    </>
  );
}

function DashboardApp() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<DashboardLayout />}>
          <Route path="stats" element={<StatsPage />} />
        </Route>
      </Routes>
    </BrowserRouter>
  );
}

export default DashboardApp;

Questions and answers

Review the lesson questions together with their answers. To test yourself, solve the lesson interactively.

1 Which is NOT one of the things you learned in this project?

  • useTransition Correct answer
  • Add via modal
  • StatCard component
  • Nested routing

Explanation: useTransition was not covered in this project.

2 Close the modal

Question
function handleClose() {
  setIsOpen(____);
}
Answer
function handleClose() {
  setIsOpen(false);
}

Explanation: Setting isOpen to false closes the modal.

3 Add via modal: match the terms

Term Meaning
BrowserRouter Router shell
Outlet Child slot
NavLink Menu link
StatCard Data card

All lessons in Intermediate