GitHub

Data Visualization

Composable chart components using Recharts with responsive containers, custom tooltips, and clean data transformation patterns.

01

Principle

Charts are pure functions of data. Keep transformation logic outside components — map raw API responses to the exact shape your chart library expects before they reach the JSX. This keeps components declarative and makes the data pipeline independently testable.

lightbulb

Always wrap charts in a ResponsiveContainer from Recharts. Hard-coded pixel widths break on resize and cause layout shifts on mobile.

02

Rules

  • check_circle
    Transform Before RenderDerive chart-ready data with useMemo. Never transform inside JSX or inside the chart component itself.
  • check_circle
    ResponsiveContainer AlwaysEvery chart must be wrapped in <ResponsiveContainer width='100%' height={300}>. Never pass fixed pixel widths.
  • check_circle
    Custom Tooltip ComponentBuild a typed CustomTooltip component instead of using Recharts' default. Gives full styling control and type safety.
  • check_circle
    Skeleton on LoadingShow a pulse skeleton at the same dimensions as the chart. Avoid layout shift when data loads.
03

Pattern

hooks/useChartData.ts
import { useMemo } from 'react';
import { useQuery } from '@tanstack/react-query';

type ChartPoint = { month: string; revenue: number; orders: number };

export function useRevenueChart(range: string) {
  const { data, isLoading, isError } = useQuery({
    queryKey: ['metrics', 'revenue', range],
    queryFn: () => fetchRevenue(range),
    staleTime: 1000 * 60 * 5,
  });

  const chartData = useMemo<ChartPoint[]>(() => {
    if (!data) return [];
    return data.map((d) => ({
      month: formatMonth(d.date),
      revenue: d.total_revenue,
      orders: d.order_count,
    }));
  }, [data]);

  return { chartData, isLoading, isError };
}
04

Implementation

info

Version Compatibility

Requires React 19+ and the latest stable versions of all dependencies shown.

In Next.js App Router, import Recharts components only inside Client Components. Use dynamic import with ssr: false to prevent hydration mismatches from window-dependent chart code.

components/charts/RevenueChart.tsx
'use client';

import {
  ResponsiveContainer, LineChart, Line,
  XAxis, YAxis, CartesianGrid, Tooltip,
} from 'recharts';
import { useRevenueChart } from '@/hooks/useChartData';

function CustomTooltip({ active, payload, label }: TooltipProps<number, string>) {
  if (!active || !payload?.length) return null;
  return (
    <div className="rounded-lg border bg-white p-3 shadow-lg text-sm">
      <p className="font-semibold text-slate-900">{label}</p>
      <p className="text-primary">${payload[0]?.value?.toLocaleString()}</p>
    </div>
  );
}

export function RevenueChart({ range }: { range: string }) {
  const { chartData, isLoading } = useRevenueChart(range);

  if (isLoading) return <div className="h-[300px] animate-pulse rounded-xl bg-slate-100" />;

  return (
    <ResponsiveContainer width="100%" height={300}>
      <LineChart data={chartData}>
        <CartesianGrid strokeDasharray="3 3" stroke="#f1f5f9" />
        <XAxis dataKey="month" tick={{ fontSize: 12 }} />
        <YAxis tick={{ fontSize: 12 }} />
        <Tooltip content={<CustomTooltip />} />
        <Line type="monotone" dataKey="revenue" stroke="#4628F1" strokeWidth={2} dot={false} />
      </LineChart>
    </ResponsiveContainer>
  );
}
menu_book
React Patterns

Helping developers build robust React applications since 2025.

© 2025 React Patterns Cookbook. Built with ❤️ for the community.
react-principles