Fix: Recharts Not Working — Chart Not Rendering, Tooltip Missing, or ResponsiveContainer Showing Zero Height
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 visibleOr ResponsiveContainer collapses to zero height:
<ResponsiveContainer width="100%" height="100%">
<BarChart data={data}>...</BarChart>
</ResponsiveContainer>
// Zero height — chart invisibleOr the tooltip appears but shows “No data”:
<Tooltip />
// Tooltip fires on hover but content is emptyOr 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 missingWhy This Happens
Recharts renders SVG elements inside React components. Its data binding and sizing are strict:
- Charts need explicit dimensions —
LineChart,BarChart, etc. require awidthandheightprop or must be wrapped inResponsiveContainer. Without dimensions, the SVG is size zero and nothing renders. When usingResponsiveContainer, the parent element must have a defined height — percentage heights only work if the parent has a concrete pixel height. dataKeymust exactly match the property name — Recharts reads data using the string you pass todataKey. 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
XAxisandYAxis, data points render but are invisible because there’s no coordinate system. The axes also drive tooltips and legends. ResponsiveContainerneeds a fixed-height parent —height="100%"onResponsiveContainermeans “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 rechartsimport {
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
ResponsiveContainerin adivwith a fixed pixel height XAxisneeds adataKeythat matches a property in your dataLine/Bar/AreadataKeymust 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 data — Pie 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.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: i18next Not Working — Translations Missing, Language Not Switching, or Namespace Errors
How to fix i18next issues — react-i18next setup, translation file loading, namespace configuration, language detection, interpolation, pluralization, and Next.js integration.
Fix: Mapbox GL JS Not Working — Map Not Rendering, Markers Missing, or Access Token Invalid
How to fix Mapbox GL JS issues — access token setup, React integration with react-map-gl, markers and popups, custom layers, geocoding, directions, and Next.js configuration.
Fix: React PDF Not Working — PDF Not Rendering, Worker Error, or Pages Blank
How to fix react-pdf and @react-pdf/renderer issues — PDF viewer setup, worker configuration, page rendering, text selection, annotations, and generating PDFs in React.
Fix: Million.js Not Working — Compiler Errors, Components Not Optimized, or React Compatibility Issues
How to fix Million.js issues — compiler setup with Vite and Next.js, block() optimization rules, component compatibility constraints, automatic mode, and debugging performance gains.