React
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.
Exercise 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.
Show example solution
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;
Test yourself
-
Which is NOT one of the things you learned in this project?
- useTransition
- Add via modal
- StatCard component
- Nested routing
Show answer
useTransition — useTransition was not covered in this project.
-
Close the modal
function handleClose() {
setIsOpen(____);
}
Show answer
function handleClose() {
setIsOpen(false);
}
-
Add via modal: match the terms
- BrowserRouter
- Router shell
- Outlet
- Child slot
- NavLink
- Menu link
- StatCard
- Data card
Next lesson: Challenge: useFetch hook