DEV Community

Cover image for How to Build Dynamic Charts in React with Recharts (Including Edge Cases)
caleb-ali
caleb-ali

Posted on

2 1 1

How to Build Dynamic Charts in React with Recharts (Including Edge Cases)

Introduction

Visualizing data is crucial for understanding trends and distributions in web applications. Recharts, a popular React charting library, makes it easy to create interactive and responsive charts by combining the power of D3 with the flexibility of React components. It offers a sweet spot between customization and ease of use, whether you're building analytics dashboards or simple data displays while keeping bundle size in check.

In this article, we'll explore:

  • Building a Line Chart (for time-based trends)

  • Building a Pie Chart (for percentage distributions)

  • Handling Edge Cases in a Pie Chart (e.g when one category is 100%)

  • Dynamic Data Fetching & Optimization (using useMemo for performance)

Setting Up Recharts

First, install Recharts in your React project:

npm install recharts
Enter fullscreen mode Exit fullscreen mode

Building a Line Chart (for time-based trends)
A line chart is ideal for showing trends over time (e.g., ticket sales by month).

Key Features:
Displays time-based data

Supports tooltips, legends, and custom styling

Responsive design

Implementation:

import "./styles.css";
import {
  LineChart, 
  Line,
  XAxis,
  YAxis,
  CartesianGrid,
  Tooltip,
  Legend,
  ResponsiveContainer,
} from "recharts";

const data = [
  {
    name: "jan",
    free: 1000,


  },
  {
    name: "feb",
    free: 1400,


  },
  {
    name: "mar",
    free: 800,


  },
  {
    name: "apr",
    free: 1000,


  },
  {
    name: "may",
    free: 800,



  },
  {
    name: "jun",
    free: 1000,


  },
  {
    name: "jul",
    free: 2000,


  },
  {
    name: "aug",
    free: 1300,



  },
  {
    name: "sep",
    free: 1000,



  },
  {
    name: "oct",
    free: 1000,



  },
  {
    name: "nov",
    free: 1000,



  },
  {
    name: "dec",
    free: 1000,


  }

];

export default function App() {
  return (
    <LineChart
      width={500}
      height={300}
      data={data} // Data array (each object represents a point on the chart)
      margin={{
        top: 5,
        right: 30,
        left: 20,
        bottom: 5,
      }}
    >
      <CartesianGrid strokeDasharray="3 3" /> // Grid lines (dashed style)
        <XAxis 
    dataKey="name"  
    padding={{ left: 20, right: 20 }}  
  />
      <YAxis />
      <Tooltip />

      <Line
        type="monotone"
        dataKey="free"
        stroke="#8884d8"
        activeDot={{ r: 8 }}
      />

    </LineChart>
  );
}

Enter fullscreen mode Exit fullscreen mode

single dataset

Comparing multiple datasets in a Line chart

import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts';

const data = [
  {
    name: "jan",
    free: 1000,
    paid: 500,
  },
  {
    name: "feb",
    free: 1400,
    paid: 800,
  },
  {
    name: "mar",
    free: 800,
    paid: 400,
  },
  {
    name: "apr",
    free: 1000,
    paid: 900,
  },
  {
    name: "may",
    free: 800,
    paid: 1200,
  },
  {
    name: "jun",
    free: 1000,
    paid: 400,
  },
  {
    name: "jul",
    free: 2000,
    paid: 1200,
  },
  {
    name: "aug",
    free: 1300,
    paid: 900,
  },
  {
    name: "sep",
    free: 1000,
    paid: 300,
  },
  {
    name: "oct",
    free: 1000,
    paid: 600,
  },
  {
    name: "nov",
    free: 1000,
    paid: 800,
  },
  {
    name: "dec",
    free: 1000,
    paid: 400,
  },
];

export default function App() {
  return (
    <LineChart
      width={500}
      height={300}
      data={data}
      margin={{
        top: 5,
        right: 30,
        left: 20,
        bottom: 5,
      }}
    >
      <CartesianGrid strokeDasharray="3 3" />
      <XAxis dataKey="name" padding={{ left: 20, right: 20 }} />
      <YAxis />
      <Tooltip />
      <Legend />
      <Line
        type="monotone"
        dataKey="free"
        stroke="#8884d8"
        activeDot={{ r: 8 }}
      />
      <Line type="monotone" dataKey="paid" stroke="#82ca9d" />
    </LineChart>
  );
}

Enter fullscreen mode Exit fullscreen mode

multiple dataset

Building a Pie Chart

We'll look at both the basic implementation and the improved version with edge case handling.

Basic Implementation

import { PieChart, Pie, Cell } from "recharts";

const data = [
  { name: "Free", value: 400 },
  { name: "Paid", value: 300 },
];

const COLORS = ["#0088FE", "#00C49F"];

const RADIAN = Math.PI / 180;
const renderCustomizedLabel = ({
  cx,
  cy,
  midAngle,
  innerRadius,
  outerRadius,
  percent,
  index,
}) => {
  const radius = innerRadius + (outerRadius - innerRadius) * 0.5;
  const x = cx + radius * Math.cos(-midAngle * RADIAN);
  const y = cy + radius * Math.sin(-midAngle * RADIAN);

  return (
    <text
      x={x}
      y={y}
      fill="white"
      textAnchor={x > cx ? "start" : "end"}
      dominantBaseline="central"
    >
      {`${(percent * 100).toFixed(0)}%`}
    </text>
  );
};
export default function App() {
  return (
    <PieChart width={400} height={400}>
      <Pie
        data={data}
        cx={200}
        cy={200}
        labelLine={false}
        label={renderCustomizedLabel}
        outerRadius={80}
        fill="#8884d8"
        dataKey="value"
      >
        {data.map((entry, index) => (
          <Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} />
        ))}
      </Pie>
    </PieChart>
  );
}

Enter fullscreen mode Exit fullscreen mode

basic piechart

Limitations of this approach:

  • When one value is 100% and the other is 0%, the chart shows a tiny white line

  • The labels can become cramped or overlap in edge cases

  • There's no special handling for 100% dominance cases

edge case

Here's the improved version that properly handles all cases:

import { PieChart, Pie, Cell, ResponsiveContainer } from "recharts";

const data = [
  { name: "Free", value: 100 },
  { name: "Paid", value: 0 },
];

const COLORS = ["#0088FE", "#00C49F"];

const RADIAN = Math.PI / 180;

const renderCustomizedLabel = ({
  cx,
  cy,
  midAngle,
  innerRadius,
  outerRadius,
  percent,
  index,
}) => {
  const has100Percent = data.some((item) => item.value === 100);

  if (has100Percent) {
    const dominantItem = data.find((item) => item.value === 100);
    return (
      <text
        x={cx}
        y={cy}
        fill="white"
        textAnchor="middle"
        dominantBaseline="central"
        style={{ fontSize: "16px", fontWeight: "bold" }}
      >
        {`${dominantItem.name}: 100%`}
      </text>
    );
  }

  return null;
};

const PieChartComponent = () => {
  // Filter out 0% values
  const filteredData = data.filter((item) => item.value > 0);

  return (
    <ResponsiveContainer width="100%" height={400}>
      <PieChart>
        <Pie
          data={filteredData} // Use filtered data here
          cx="50%"
          cy="50%"
          labelLine={false}
          label={renderCustomizedLabel}
          outerRadius={80}
          fill="#8884d8"
          dataKey="value"
          stroke="none"
        >
          {filteredData.map((entry, index) => (
            <Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} />
          ))}
        </Pie>
      </PieChart>
    </ResponsiveContainer>
  );
};

export default PieChartComponent;

Enter fullscreen mode Exit fullscreen mode

fixed edgecase

Finally we'll explore how to fetch and display dynamic data in your charts, focusing on performance optimization with useMemo

Why Use useMemo for Dynamic Charts?
When working with dynamic data in React charts, useMemo provides two key benefits:

  • Performance Optimization: It prevents unnecessary recalculations of your chart data on every render

  • Stable References: It ensures your chart components don't re-render unless the actual data changes

Dynamic Line Chart Implementation
Here's how to implement a dynamic line chart that works with fetched data

import { useState, useEffect, useMemo } from 'react';
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts';
import { useSalesData } from "lib/hooks";


const DynamicLineChart = ({ eventId }) => {

//assuming useSalesData is the hook i am fetching my sales data from
  const { data: salesData, loading } = useSalesData(eventId);

  // Transform data for the chart
  const chartData = useMemo(() => {
    if (!salesData?.monthlySales) return [];

    return salesData.monthlySales.map(month => ({
      name: month.month,
      free: month.free,
      paid: month.paid
    }));
  }, [salesData]);

  if (loading) return <div>Loading chart data...</div>;

  return (
    <ResponsiveContainer width="100%" height={300}>
      <LineChart
        data={chartData}
        margin={{ top: 5, right: 30, left: 20, bottom: 5 }}
      >
        <CartesianGrid strokeDasharray="3 3" />
        <XAxis dataKey="name" />
        <YAxis />
        <Tooltip />
        <Legend />
        <Line 
          type="monotone" 
          dataKey="free" 
          stroke="#8884d8" 
          activeDot={{ r: 8 }} 
        />
        <Line 
          type="monotone" 
          dataKey="paid" 
          stroke="#82ca9d" 
        />
      </LineChart>
    </ResponsiveContainer>
  );
};

export default DynamicLineChart;
Enter fullscreen mode Exit fullscreen mode

Dynamic Pie Chart Implementation

Here's how to implement a dynamic pie chart that works with fetched data

import { useState, useEffect, useMemo } from 'react';
import { PieChart, Pie, Cell, ResponsiveContainer } from 'recharts';
import { useTicketTypeData } from "lib/hooks";


const COLORS = ['#0088FE', '#00C49F'];

const DynamicPieChart = ({ eventId }) => {
//assuming useTicketTypeData is the hook i am fetching my ticket type data from
  const { data: ticketData, loading } = useTicketTypeData(eventId);

  // Transform data for the pie chart
  const pieData = useMemo(() => {
    if (!ticketData?.ticketTypes) return [];

    const total = ticketData.ticketTypes.free + ticketData.ticketTypes.paid;
    return [
      { 
        name: 'Free', 
        value: total > 0 ? (ticketData.ticketTypes.free / total) * 100 : 0 
      },
      { 
        name: 'Paid', 
        value: total > 0 ? (ticketData.ticketTypes.paid / total) * 100 : 0 
      }
    ];
  }, [ticketData]);

  if (loading) return <div>Loading pie chart data...</div>;

  return (
    <ResponsiveContainer width="100%" height={300}>
      <PieChart>
        <Pie
          data={pieData}
          cx="50%"
          cy="50%"
          labelLine={false}
          label={({ name, percent }) => `${name}: ${(percent * 100).toFixed(0)}%`}
          outerRadius={80}
          fill="#8884d8"
          dataKey="value"
        >
          {pieData.map((entry, index) => (
            <Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} />
          ))}
        </Pie>
      </PieChart>
    </ResponsiveContainer>
  );
};

export default DynamicPieChart;
Enter fullscreen mode Exit fullscreen mode

Conclusion
Recharts provides a powerful yet simple way to create dynamic, interactive data visualizations in React. By combining efficient data fetching with smart memoization techniques, you can build performant dashboards that gracefully handle real-world data scenarios. Remember to:

  • Always memoize data transformations with useMemo

  • Handle loading and error states

  • Consider edge cases like 100% values in pie charts

  • Leverage Recharts' responsive containers for mobile-friendly displays

With these patterns, you're well-equipped to create production-ready visualizations that bring your data to life.

References & Further Reading

Top comments (0)

ACI image

ACI.dev: Fully Open-source AI Agent Tool-Use Infra (Composio Alternative)

100% open-source tool-use platform (backend, dev portal, integration library, SDK/MCP) that connects your AI agents to 600+ tools with multi-tenant auth, granular permissions, and access through direct function calling or a unified MCP server.

Check out our GitHub!