In short
Dashboards are what a lot of companies actually build in React. Cards, charts, filters and tables pulling from an API will make you very comfortable with data and effects.
A data-dense admin view combining stat cards, charts and tables — practice for composing many components together and managing shared/derived state.

Think of it like this
Your banking app's home screen
One glance shows your balance, recent spends, a pie chart of where the money went and a filter for last month. Nothing fancy on its own, but it all reads from the same data and updates together. That's a dashboard.
Watch it happen
The slow June response
month: June
1/7You pick June, and useSales runs its effect for June.
The big ideas
What you need to know
Grid layout
A sidebar, a top bar and a CSS grid of cards that reflows nicely on mobile.
Loading, error and data
Every widget has three states. Plan all three from day one, not just the happy path.
Charts from a library
Use a library like Recharts instead of drawing SVG by hand, and feed it derived data.
One filter, many widgets
A date range lives in the parent, and every card recalculates from it.
How it works
Build plan
- 1
Shell
Sidebar, header and an empty grid of cards.
- 2
Fake data
A JSON file of sales with dates, amounts and categories.
- 3
Stat cards
Totals and averages derived from the data.
- 4
Chart and table
A line chart over time and a sortable table.
- 5
Filters
A month or date range that every widget responds to.
See it in code
Walk through the code
1import { useEffect, useState } from "react";2 3type Sale = { date: string; amount: number };4export function useSales(month: string) {5 const [sales, setSales] = useState<Sale[] | null>(null);6 const [error, setError] = useState<string | null>(null);7 useEffect(() => {8 let ignore = false;9 fetch("/api/sales?month=" + month)10 .then((r) => (r.ok ? r.json() : Promise.reject(new Error("HTTP " + r.status))))11 .then((data) => { if (!ignore) setSales(data); })12 .catch((e) => { if (!ignore) setError(e.message); });13 return () => { ignore = true; };14 }, [month]);15 return { sales, error, loading: sales === null && error === null };16}1/4Two pieces of state: the sales data and any error.
If the user switches months quickly, the ignore flag drops the old response so it can't overwrite the newer one. In bigger apps, TanStack Query handles this for you.
Save yourself the pain
Common mistakes
Oops
Fetching in useEffect without any cleanup.
Do this instead
A slow old response can land after a new one and show the wrong month. Use an ignore flag or an AbortController.
Oops
Drawing every chart by hand in SVG.
Do this instead
Use a chart library and spend your time on the data and layout. That's what the job usually needs.
Your mission
First milestone: three stat cards
Quick check
A user quickly switches from June to July, and June's slow response arrives last. Without cleanup, what shows?
Go deeper
- 1Break the dashboard into small, independent components (StatCard, ChartPanel, etc.).
- 2Use derived/computed values rather than duplicating state.
- 3Add loading skeletons for each panel independently.
More projects at this stop
Stuck on this topic? Ask an Insider
Get 1:1 guidance from people who've walked this exact path — on the InsideEdge app.