Skip to content

Fix: Recharts Not Working — Chart Not Rendering, Tooltip Missing, or ResponsiveContainer Showing Zero Height

FixDevs ·

Quick Answer

How to fix Recharts issues — ResponsiveContainer setup, data format for each chart type, custom tooltips, axis configuration, legends, animations, and TypeScript types.

The Problem

The chart renders nothing — just an empty area:

import { LineChart, Line } from 'recharts';

function MyChart() {
  const data = [
    { name: 'Jan', value: 100 },
    { name: 'Feb', value: 200 },
  ];

  return (
    <LineChart data={data}>
      <Line type="monotone" dataKey="value" />
    </LineChart>
  );
}
// Empty chart — no lines, no axes visible

Or ResponsiveContainer collapses to zero height:

<ResponsiveContainer width="100%" height="100%">
  <BarChart data={data}>...</BarChart>
</ResponsiveContainer>
// Zero height — chart invisible

Or the tooltip appears but shows “No data”:

<Tooltip />
// Tooltip fires on hover but content is empty

Or custom data doesn’t match what Recharts expects:

const data = [{ x: 1, y: 40 }, { x: 2, y: 60 }];
// Bar chart shows bars at correct positions but labels are missing

Why This Happens

Recharts renders SVG elements inside React components. Its data binding and sizing are strict:

  • Charts need explicit dimensionsLineChart, BarChart, etc. require a width and height prop or must be wrapped in ResponsiveContainer. Without dimensions, the SVG is size zero and nothing renders. When using ResponsiveContainer, the parent element must have a defined height — percentage heights only work if the parent has a concrete pixel height.
  • dataKey must exactly match the property name — Recharts reads data using the string you pass to dataKey. A typo or case mismatch silently renders nothing without an error. Object nesting requires dot-notation (dataKey="user.age") or a function.
  • Axes determine the visible range — without XAxis and YAxis, data points render but are invisible because there’s no coordinate system. The axes also drive tooltips and legends.
  • ResponsiveContainer needs a fixed-height parentheight="100%" on ResponsiveContainer means “take 100% of the parent’s height.” If the parent is a flex container with no explicit height, its height is determined by its children — creating a circular dependency that resolves to zero.

Fix 1: Render a Basic Chart Correctly

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

const data = [
  { month: 'Jan', revenue: 4000, expenses: 2400 },
  { month: 'Feb', revenue: 3000, expenses: 1398 },
  { month: 'Mar', revenue: 2000, expenses: 9800 },
  { month: 'Apr', revenue: 2780, expenses: 3908 },
  { month: 'May', revenue: 1890, expenses: 4800 },
  { month: 'Jun', revenue: 2390, expenses: 3800 },
];

function RevenueChart() {
  return (
    // ResponsiveContainer requires a parent with a defined height
    <div style={{ width: '100%', height: 400 }}>
      <ResponsiveContainer width="100%" height="100%">
        <LineChart
          data={data}
          margin={{ top: 5, right: 30, left: 20, bottom: 5 }}
        >
          <CartesianGrid strokeDasharray="3 3" />
          <XAxis dataKey="month" />
          <YAxis />
          <Tooltip />
          <Legend />
          <Line
            type="monotone"
            dataKey="revenue"
            stroke="#8884d8"
            strokeWidth={2}
            dot={{ fill: '#8884d8' }}
            activeDot={{ r: 8 }}
          />
          <Line
            type="monotone"
            dataKey="expenses"
            stroke="#82ca9d"
            strokeWidth={2}
          />
        </LineChart>
      </ResponsiveContainer>
    </div>
  );
}

Key rules:

  • Wrap ResponsiveContainer in a div with a fixed pixel height
  • XAxis needs a dataKey that matches a property in your data
  • Line / Bar / Area dataKey must match a numeric property in your data
  • Add <Tooltip /> and <Legend /> explicitly — they’re not included by default

Fix 2: Fix ResponsiveContainer Height

The most common Recharts issue: the chart is invisible because the container has zero height.

// WRONG — parent has no height, container collapses to 0
<div>
  <ResponsiveContainer width="100%" height="100%">
    <LineChart data={data}>...</LineChart>
  </ResponsiveContainer>
</div>

// WRONG — flex parent with no height
<div style={{ display: 'flex' }}>
  <ResponsiveContainer width="100%" height="100%">
    <LineChart data={data}>...</LineChart>
  </ResponsiveContainer>
</div>

// CORRECT — parent has explicit height
<div style={{ width: '100%', height: 400 }}>
  <ResponsiveContainer width="100%" height="100%">
    <LineChart data={data}>...</LineChart>
  </ResponsiveContainer>
</div>

// CORRECT — use pixel height directly on ResponsiveContainer
<ResponsiveContainer width="100%" height={400}>
  <LineChart data={data}>...</LineChart>
</ResponsiveContainer>

// CORRECT — flex parent with explicit height
<div style={{ display: 'flex', flexDirection: 'column', height: '500px' }}>
  <h2>Revenue</h2>
  <div style={{ flex: 1 }}>  {/* flex: 1 expands to fill remaining height */}
    <ResponsiveContainer width="100%" height="100%">
      <LineChart data={data}>...</LineChart>
    </ResponsiveContainer>
  </div>
</div>

Tailwind CSS — use h- classes on the wrapper:

<div className="w-full h-96">  {/* h-96 = 384px */}
  <ResponsiveContainer width="100%" height="100%">
    <LineChart data={data}>...</LineChart>
  </ResponsiveContainer>
</div>

Fix 3: Bar, Area, and Pie Charts

Each chart type has its own data requirements:

import { BarChart, Bar, AreaChart, Area, PieChart, Pie, Cell } from 'recharts';

// Bar chart — stacked bars
const barData = [
  { category: 'Electronics', q1: 4000, q2: 3000, q3: 2000, q4: 2780 },
  { category: 'Clothing', q1: 3000, q2: 2000, q3: 5000, q4: 3908 },
  { category: 'Food', q1: 2000, q2: 4000, q3: 3000, q4: 4800 },
];

<BarChart data={barData} width={600} height={400}>
  <CartesianGrid strokeDasharray="3 3" />
  <XAxis dataKey="category" />
  <YAxis />
  <Tooltip />
  <Legend />
  <Bar dataKey="q1" stackId="a" fill="#8884d8" name="Q1" />
  <Bar dataKey="q2" stackId="a" fill="#82ca9d" name="Q2" />
  <Bar dataKey="q3" stackId="a" fill="#ffc658" name="Q3" />
  <Bar dataKey="q4" stackId="a" fill="#ff7300" name="Q4" />
</BarChart>

// Area chart — gradient fill
const areaData = [
  { time: '00:00', users: 100 },
  { time: '06:00', users: 300 },
  { time: '12:00', users: 800 },
  { time: '18:00', users: 600 },
  { time: '24:00', users: 200 },
];

<AreaChart data={areaData}>
  <defs>
    <linearGradient id="colorUsers" x1="0" y1="0" x2="0" y2="1">
      <stop offset="5%" stopColor="#8884d8" stopOpacity={0.8} />
      <stop offset="95%" stopColor="#8884d8" stopOpacity={0} />
    </linearGradient>
  </defs>
  <XAxis dataKey="time" />
  <YAxis />
  <CartesianGrid strokeDasharray="3 3" />
  <Tooltip />
  <Area
    type="monotone"
    dataKey="users"
    stroke="#8884d8"
    fillOpacity={1}
    fill="url(#colorUsers)"
  />
</AreaChart>

// Pie chart — data format is different
const pieData = [
  { name: 'Chrome', value: 65 },
  { name: 'Firefox', value: 15 },
  { name: 'Safari', value: 12 },
  { name: 'Other', value: 8 },
];
const COLORS = ['#0088FE', '#00C49F', '#FFBB28', '#FF8042'];

<PieChart width={400} height={400}>
  <Pie
    data={pieData}
    cx="50%"
    cy="50%"
    outerRadius={150}
    dataKey="value"          // Must be a numeric field
    nameKey="name"           // Used in tooltip and legend
    label={({ name, percent }) =>
      `${name}: ${(percent * 100).toFixed(0)}%`
    }
  >
    {pieData.map((entry, index) => (
      <Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} />
    ))}
  </Pie>
  <Tooltip />
  <Legend />
</PieChart>

Fix 4: Custom Tooltips

The default tooltip often needs customization for real apps:

import { TooltipProps } from 'recharts';
import { NameType, ValueType } from 'recharts/types/component/DefaultTooltipContent';

// Custom tooltip component
function CustomTooltip({ active, payload, label }: TooltipProps<ValueType, NameType>) {
  if (!active || !payload || payload.length === 0) return null;

  return (
    <div
      style={{
        background: 'white',
        border: '1px solid #ccc',
        borderRadius: '8px',
        padding: '12px',
        boxShadow: '0 2px 8px rgba(0,0,0,0.15)',
      }}
    >
      <p style={{ margin: 0, fontWeight: 'bold' }}>{label}</p>
      {payload.map((entry, index) => (
        <p key={index} style={{ color: entry.color, margin: '4px 0' }}>
          {entry.name}: {typeof entry.value === 'number'
            ? `$${entry.value.toLocaleString()}`
            : entry.value
          }
        </p>
      ))}
    </div>
  );
}

// Use in chart
<LineChart data={data}>
  <Tooltip content={<CustomTooltip />} />
  <Line dataKey="revenue" stroke="#8884d8" />
</LineChart>

// Formatter approach — simpler for quick customization
<Tooltip
  formatter={(value: number, name: string) => [
    `$${value.toLocaleString()}`,  // Formatted value
    name.charAt(0).toUpperCase() + name.slice(1),  // Formatted label
  ]}
  labelFormatter={(label) => `Month: ${label}`}
/>

Fix 5: Axis Configuration

Axes control the coordinate system, tick labels, and data range:

import { XAxis, YAxis, ReferenceLine } from 'recharts';

<LineChart data={data}>
  <XAxis
    dataKey="date"
    // Custom tick formatter
    tickFormatter={(value) => new Date(value).toLocaleDateString('en-US', {
      month: 'short',
      day: 'numeric',
    })}
    // Rotate labels to avoid overlap
    angle={-45}
    textAnchor="end"
    height={60}
    // Show only every Nth tick
    interval={6}
  />

  <YAxis
    // Fixed domain instead of auto-calculated
    domain={[0, 'auto']}
    // domain={[0, 10000]}  // Fixed range
    // domain={['dataMin - 100', 'dataMax + 100']}  // Relative to data
    // Format Y axis labels
    tickFormatter={(value) => `$${(value / 1000).toFixed(0)}k`}
    // Add unit label
    label={{ value: 'Revenue (USD)', angle: -90, position: 'insideLeft' }}
    width={80}
  />

  {/* Dual Y axes */}
  <YAxis yAxisId="left" orientation="left" stroke="#8884d8" />
  <YAxis yAxisId="right" orientation="right" stroke="#82ca9d" />
  <Line yAxisId="left" dataKey="revenue" stroke="#8884d8" />
  <Line yAxisId="right" dataKey="users" stroke="#82ca9d" />

  {/* Reference lines — averages, targets */}
  <ReferenceLine y={5000} label="Target" stroke="red" strokeDasharray="3 3" />
  <ReferenceLine x="Jun" label="Launch" stroke="green" />
</LineChart>

Fix 6: Performance and Animations

Large datasets cause slow renders:

import { LineChart, Line } from 'recharts';

// Disable animation for large datasets
<Line
  dataKey="value"
  stroke="#8884d8"
  isAnimationActive={false}  // Disable animation
  // animationDuration={300}  // Or speed it up (default: 1500ms)
  // animationEasing="ease-in-out"
  dot={false}  // Hide dots on large datasets — huge performance win
/>

// Memoize data to prevent unnecessary re-renders
import { useMemo } from 'react';

function Chart({ rawData }) {
  // Transform data once — don't do this inside render
  const chartData = useMemo(() =>
    rawData.map(row => ({
      date: new Date(row.timestamp).toLocaleDateString(),
      value: Math.round(row.value),
    })),
    [rawData]
  );

  return (
    <ResponsiveContainer width="100%" height={400}>
      <LineChart data={chartData}>
        <Line dataKey="value" dot={false} isAnimationActive={false} />
      </LineChart>
    </ResponsiveContainer>
  );
}

// Sample large datasets before rendering
function sampleData(data: any[], maxPoints: number) {
  if (data.length <= maxPoints) return data;
  const step = Math.ceil(data.length / maxPoints);
  return data.filter((_, i) => i % step === 0);
}

const chartData = useMemo(() => sampleData(rawData, 500), [rawData]);

Still Not Working?

dataKey renders but values are all zero — the key matches but the values are strings, not numbers. Recharts plots the numeric value of each data point. If your API returns { value: "100" } (string), the chart treats it as NaN. Parse the values: data.map(d => ({ ...d, value: Number(d.value) })).

Legend shows but clicking it doesn’t hide the line — hiding/showing series on legend click isn’t built-in to Recharts. You need to manage it yourself with state and conditional rendering, or use the hide prop on <Line>. Track which keys are hidden in a Set and update on the onClick prop of <Legend>.

Chart flickers or re-renders on every parent render — check whether your data array is being re-created on each render. If data is defined inline (e.g., data={rows.map(...)} directly in JSX), React creates a new array reference every render, which causes Recharts to re-animate. Memoize the data transformation with useMemo.

Pie chart shows “No data” with correct dataPie requires dataKey to point to a numeric field. Also, PieChart does not accept a top-level data prop like LineChart — the data goes directly on the <Pie> component, not on <PieChart>.

For related data visualization issues, see Fix: React useState Not Updating and Fix: TanStack Query Not Working.

F

FixDevs

Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.

Was this article helpful?

Related Articles