⚛️ React · Intermédiaire

Dashboard de visualisation avec Recharts

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

Ce tutoriel construit un tableau de bord analytique complet : KPI cards, graphiques en ligne, barres et camembert, avec filtre temporel, chargement de données simulé depuis une API et design responsive — le type de dashboard qu'on retrouve en entreprise.

1. Créer le projet

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. Hook useMetrics — données et état

On commence par le hook custom qui gère le chargement des données et le filtre temporel.

📖 Terme : Hook React

Définition : Fonction spéciale React (commençant par "use") qui permet d'utiliser des features React dans les composants fonctionnels. Les hooks incluent les hooks built-in (useState, useEffect, useMemo, useCallback) et les hooks custom que vous créez.

But : Encapsuler la logique réutilisable (état, effets) dans des fonctions réutilisables plutôt que de dupliquer le code.

Pourquoi ici : Les hooks custom comme useMetrics permettent d'extraire toute la logique de données (chargement, filtrage, calculs) du composant. D'autres composants peuvent réutiliser ce hook — c'est la réutilisation de logique horizontale.

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

// ── Générer des données simulées (remplacer par un vrai appel API) ──
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);
    // Variation réaliste avec tendance haussière
    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: 'Organique', 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);  # jours
  const [loading, setLoading] = useState(false);

  // Simuler un chargement API avec délai
  const changeRange = useCallback((days) => {
    setLoading(true);
    setTimeout(() => { setRange(days); setLoading(false); }, 400);
  }, []);

  // Données filtrées selon la période sélectionnée
  const filteredData = useMemo(() => {
    const cutoff = subDays(new Date(), range);
    return allData.filter(d => isAfter(parseISO(d.date), cutoff));
  }, [allData, range]);

  // KPIs calculés une seule fois quand filteredData change
  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 };
}
📖 Terme : useMemo

Définition : Hook React qui mémoïse (cache) une valeur calculée. Si les dépendances n'ont pas changé, useMemo retourne la valeur en cache au lieu de la recalculer.

But : Éviter les recalculs coûteux à chaque render — essentiel pour les calculs complexes (filtrages, agrégations) et la performance.

Pourquoi ici : filteredData et kpis sont recalculés seulement quand leur dépendances (allData, range) changent. Sans useMemo, chaque render recalculerait ces valeurs, ce qui est lent si on a 10 000 data points et on renderise par exemple 100x par seconde.

Le hook useMetrics encapsule : (1) l'état allData (90 jours de données), (2) le range (7/30/90 jours), (3) le loading simulé, (4) les données filtrées selon le range avec useMemo, (5) les KPIs calculés (revenu total, croissance, conversion rate) avec useMemo. Quand un composant appelle useMetrics(), il reçoit all these things et peut les utiliser.
📖 Terme : useCallback

Définition : Hook React qui mémoïse une fonction. Si les dépendances ne changent pas, useCallback retourne la même référence de fonction au lieu de créer une nouvelle chaque render.

But : Éviter que les fonctions callback ne triggent inutilement les re-renders des composants enfants qui reçoivent cette fonction en prop.

Pourquoi ici : changeRange est passée aux boutons de filtre. Sans useCallback, chaque render du parent crée une nouvelle fonction changeRange, ce qui force les enfants à re-render même si le comportement n'a pas changé.

3. Composant KPI Card

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 période préc.
          </p>
        )}
      </div>
    </div>
  );
}
Le composant KPICard affiche : icône + titre + valeur + variation par rapport à la période précédente. Un composant réutilisable pour afficher n'importe quel KPI avec couleur customizable. Les props title, value, change, icon sont tous passés du Dashboard parent.

4. Dashboard principal

src/Dashboard.jsx (extrait)
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 pour les graphiques ──
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>
  );
}
📖 Terme : ResponsiveContainer (Recharts)

Définition : Composant Recharts qui rend les graphiques responsive — ils s'adaptent à la taille du container au lieu d'avoir une taille fixe. Crucial pour les dashboards mobile-friendly.

But : Les graphiques restent lisibles sur tous les écrans (mobile, tablet, desktop) sans avoir à mettre à jour les dimensions manuellement.

Pourquoi ici : Sans ResponsiveContainer, les graphiques ont une taille fixe et débordent sur mobile. Avec ResponsiveContainer, ils s'adaptent flexiblement.

src/Dashboard.jsx (graphiques)
// ── Graphique en ligne : revenus quotidiens ──
<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="Revenu" stroke="#6366f1" strokeWidth={2} dot={false} />
  </LineChart>
</ResponsiveContainer>

// ── Camembert : sources de trafic ──
<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>

// ── Barres : sessions & conversions hebdomadaires ──
<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>
Les trois graphiques Recharts : LineChart pour les trends, PieChart pour les distributions, BarChart pour les comparaisons côte à côte. Chacun est enveloppé dans ResponsiveContainer et reçoit des données du hook useMetrics. Le CustomTooltip affiche des infos au survol.
📖 Terme : Composant (state, props)

Définition : Un composant React est une fonction JavaScript qui retourne du JSX (HTML-like syntax). Props sont les arguments passés au composant (données de haut en bas), state (useState) est les données mutables du composant.

But : Décomposer l'UI en petits composants réutilisables et maintenables.

Pourquoi ici : KPICard est un composant réutilisable — on l'utilise 4 fois avec des props différentes. Sans composants, on aurait du code dupliqué partout.

📖 Terme : Rendu (render)

Définition : Le rendu est le processus de React qui prend un composant et produit du DOM (éléments HTML) basé sur l'état et les props actuels. Quand l'état change, React re-rend le composant pour refléter les changements.

But : Convertir la description déclarative d'un composant en DOM concret.

Pourquoi ici : Comprendre le rendering est clé pour optimiser les performances — React re-rend par défaut quand props/state changent, ce qui peut être lent sur de gros composants. C'est pourquoi on utilise useMemo et useCallback.

5. App.js et lancement

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

export default function App() {
  return <Dashboard />;
}
Terminal — lancer le dashboard
npm start
# Ouvre http://localhost:3000
npm start lance le serveur de développement Webpack. Le dashboard se met à jour en temps réel quand vous modifiez le code (hot reloading).
Votre dashboard est opérationnel avec :