⚛️ React · Intermediate

Dashboard Visualization with Recharts

⏱ 50 minutes⚛️ React 18📊 Recharts 2.12🎨 Tailwind CSS

This tutorial builds a complete analytics dashboard: KPI cards, line charts, bar charts and pie charts, with time filter, simulated API data loading and responsive design — the kind of dashboard you see in enterprise.

1. Create the Project

Terminal
npx create-react-app dashboard-analytics --template javascript
cd dashboard-analytics

npm install recharts date-fns
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p

2. useMetrics Hook — Data and State

We start with a custom hook that manages data loading and time filtering.

📖 Term: React Hook

Definition: Special React function (starting with "use") that enables using React features in functional components. Hooks include built-in hooks (useState, useEffect, useMemo, useCallback) and custom hooks you create.

Purpose: Encapsulate reusable logic (state, effects) in reusable functions rather than duplicating code.

Why here: Custom hooks like useMetrics allow extracting all data logic (loading, filtering, calculations) from the component. Other components can reuse this hook — it's horizontal logic reuse.

src/hooks/useMetrics.js
import { useState, useEffect, useCallback, useMemo } from 'react';
import { subDays, format, parseISO, isAfter } from 'date-fns';

// ── Generate simulated data (replace with real API call) ──
function generateData(days = 90) {
  const data = [];
  let revenue = 5000;
  let users = 1200;

  for (let i = days; i >= 0; i--) {
    const date = subDays(new Date(), i);
    # Realistic variation with upward trend
    revenue *= (1 + (Math.random() - 0.45) * 0.08);
    users += Math.floor((Math.random() - 0.3) * 30);

    data.push({
      date: format(date, 'yyyy-MM-dd'),
      revenue: Math.max(0, Math.round(revenue)),
      users: Math.max(0, users),
      conversions: Math.round(users * (0.02 + Math.random() * 0.04)),
      sessions: Math.round(users * (1.5 + Math.random() * 0.5)),
    });
  }
  return data;
}

const TRAFFIC_SOURCES = [
  { name: 'Organic', value: 42, color: '#6366f1' },
  { name: 'Direct', value: 28, color: '#22d3ee' },
  { name: 'Social', value: 18, color: '#f59e0b' },
  { name: 'Email', value: 12, color: '#10b981' },
];

export default function useMetrics() {
  const [allData] = useState(() => generateData(90));
  const [range, setRange] = useState(30);  # days
  const [loading, setLoading] = useState(false);

  # Simulate API loading with delay
  const changeRange = useCallback((days) => {
    setLoading(true);
    setTimeout(() => { setRange(days); setLoading(false); }, 400);
  }, []);

  # Data filtered by selected time period
  const filteredData = useMemo(() => {
    const cutoff = subDays(new Date(), range);
    return allData.filter(d => isAfter(parseISO(d.date), cutoff));
  }, [allData, range]);

  # KPIs calculated once when filteredData changes
  const kpis = useMemo(() => {
    if (!filteredData.length) return {};
    const last = filteredData[filteredData.length - 1];
    const first = filteredData[0];
    const totalRevenue = filteredData.reduce((s, d) => s + d.revenue, 0);
    const revGrowth = ((last.revenue - first.revenue) / first.revenue) * 100;

    return {
      totalRevenue,
      revGrowth: revGrowth.toFixed(1),
      totalUsers: last.users,
      avgConvRate: (filteredData.reduce((s, d) => s + d.conversions / d.users, 0) / filteredData.length * 100).toFixed(2),
      totalSessions: filteredData.reduce((s, d) => s + d.sessions, 0),
    };
  }, [filteredData]);

  return { data: filteredData, kpis, range, changeRange, loading, trafficSources: TRAFFIC_SOURCES };
}
📖 Term: useMemo

Definition: React hook that memoizes (caches) a computed value. If dependencies haven't changed, useMemo returns the cached value instead of recomputing.

Purpose: Avoid expensive recalculations on each render — essential for complex computations (filtering, aggregations) and performance.

Why here: filteredData and kpis are recalculated only when their dependencies (allData, range) change. Without useMemo, every render would recalculate these values, which is slow if you have 10,000 data points and render 100x per second.

The useMetrics hook encapsulates: (1) state allData (90 days of data), (2) range (7/30/90 days), (3) simulated loading, (4) filtered data by range with useMemo, (5) calculated KPIs (total revenue, growth, conversion rate) with useMemo. When a component calls useMetrics(), it receives all these things and can use them.
📖 Term: useCallback

Definition: React hook that memoizes a function. If dependencies don't change, useCallback returns the same function reference instead of creating a new one each render.

Purpose: Prevent function callbacks from triggering unnecessary re-renders of child components that receive this function as a prop.

Why here: changeRange is passed to filter buttons. Without useCallback, every parent render creates a new changeRange function, which forces children to re-render even if behavior hasn't changed.

3. KPI Card Component

src/components/KPICard.jsx
export default function KPICard({ title, value, change, icon, color = 'indigo' }) {
  const isPositive = parseFloat(change) >= 0;

  return (
    <div className="bg-white rounded-xl shadow-sm border border-gray-100 p-5 flex items-start gap-4">
      <div className={`p-3 rounded-lg bg-${color}-50 text-${color}-600 text-2xl`}>
        {icon}
      </div>
      <div className="flex-1 min-w-0">
        <p className="text-sm text-gray-500 font-medium truncate">{title}</p>
        <p className="text-2xl font-bold text-gray-900 mt-0.5">{value}</p>
        {change !== undefined && (
          <p className={`text-sm mt-1 font-medium ${isPositive ? 'text-green-600' : 'text-red-500'}`}>
            {isPositive ? '↑' : '↓'} {Math.abs(parseFloat(change))}% vs prev period
          </p>
        )}
      </div>
    </div>
  );
}
The KPICard component displays: icon + title + value + change vs previous period. A reusable component for displaying any KPI with customizable color. Props title, value, change, icon are all passed from the Dashboard parent.

4. Main Dashboard

src/Dashboard.jsx (excerpt)
import {
  LineChart, Line, BarChart, Bar, PieChart, Pie, Cell,
  XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer
} from 'recharts';
import useMetrics from './hooks/useMetrics';
import KPICard from './components/KPICard';

// ── Custom Tooltip for charts ──
function CustomTooltip({ active, payload, label }) {
  if (!active || !payload?.length) return null;
  return (
    <div className="bg-gray-900 text-white p-3 rounded-lg shadow-xl text-sm">
      <p className="font-semibold mb-2">{label}</p>
      {payload.map((p, i) => (
        <p key={i} style={{ color: p.color }}>
          {p.name} : <strong>{p.value}</strong>
        </p>
      ))}
    </div>
  );
}
📖 Term: ResponsiveContainer (Recharts)

Definition: Recharts component that renders charts responsive — they adapt to container size instead of having a fixed size. Crucial for mobile-friendly dashboards.

Purpose: Charts stay readable on all screens (mobile, tablet, desktop) without manually updating dimensions.

Why here: Without ResponsiveContainer, charts have fixed size and overflow on mobile. With ResponsiveContainer, they adapt flexibly.

src/Dashboard.jsx (charts)
# ── Line chart: daily revenue ──
<ResponsiveContainer width="100%" height={260}>
  <LineChart data={data} margin={{ top: 5, right: 10, bottom: 5, left: 0 }}>
    <CartesianGrid strokeDasharray="3 3" stroke="#f1f5f9" />
    <XAxis dataKey="date" tickFormatter={formatDate} />
    <YAxis tickFormatter={formatCurrency} />
    <Tooltip content={<CustomTooltip />} />
    <Line type="monotone" dataKey="revenue" name="Revenue" stroke="#6366f1" strokeWidth={2} dot={false} />
  </LineChart>
</ResponsiveContainer>

# ── Pie chart: traffic sources ──
<ResponsiveContainer width="100%" height={200}>
  <PieChart>
    <Pie data={trafficSources} dataKey="value" cx="50%" cy="50%" outerRadius={80}
         label={({ name, value }) => `${name} ${value}%`} labelLine={false}>
      {trafficSources.map((entry, i) => <Cell key={i} fill={entry.color} />)}
    </Pie>
    <Tooltip />
  </PieChart>
</ResponsiveContainer>

# ── Bars: weekly sessions & conversions ──
<ResponsiveContainer width="100%" height={220}>
  <BarChart data={weeklyData}>
    <CartesianGrid strokeDasharray="3 3" />
    <XAxis dataKey="label" />
    <YAxis />
    <Tooltip content={<CustomTooltip />} />
    <Legend />
    <Bar dataKey="sessions" name="Sessions" fill="#6366f1" radius={[4, 4, 0, 0]} />
    <Bar dataKey="conversions" name="Conversions" fill="#10b981" radius={[4, 4, 0, 0]} />
  </BarChart>
</ResponsiveContainer>
Three Recharts charts: LineChart for trends, PieChart for distributions, BarChart for side-by-side comparisons. Each is wrapped in ResponsiveContainer and receives data from the useMetrics hook. CustomTooltip shows info on hover.
📖 Term: Component (state, props)

Definition: A component in React is a JavaScript function that returns JSX (HTML-like syntax). Props are arguments passed to the component (top-down data), state (useState) is mutable component data.

Purpose: Decompose UI into small reusable and maintainable components.

Why here: KPICard is a reusable component — we use it 4 times with different props. Without components, we'd have duplicated code everywhere.

📖 Term: Render

Definition: Rendering is React's process of taking a component and producing DOM (HTML elements) based on current state and props. When state changes, React re-renders the component to reflect changes.

Purpose: Convert a component's declarative description into concrete DOM.

Why here: Understanding rendering is key to optimizing performance — React re-renders by default when props/state change, which can be slow on large components. That's why we use useMemo and useCallback.

5. App.js and Launch

src/App.js
import Dashboard from './Dashboard';
import './index.css';

export default function App() {
  return <Dashboard />;
}
Terminal — Launch the dashboard
npm start
# Opens http://localhost:3000
npm start launches the Webpack dev server. The dashboard updates in real time when you edit code (hot reloading).
Your dashboard is operational with: