Fix: Recharts Not Working — Chart Not Rendering, Tooltip Missing, or ResponsiveContainer Showing Zero Height
Part of: React & Frontend Errors
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.
The most counterintuitive part of Recharts is that it’s a coordinate-system library disguised as a chart library. It doesn’t render lines or bars based on data values directly. It renders them based on what XAxis and YAxis say the visible range is. If you forget <XAxis> and <YAxis>, the chart computes a coordinate space of size zero and your data points all collapse to the origin — invisible. Adding them makes everything click. Add the axes first, then the visual marks (<Line>, <Bar>, <Area>).
The other quiet failure is data shape. Recharts expects an array of objects, where each object is one X-axis position and the various keys are the series. A common mistake: feeding in a { series: [...], labels: [...] } shape (like Chart.js wants) and seeing nothing. Recharts will not show an error — dataKey="value" against a missing key resolves to undefined, the chart treats that as no point at that position, and you get an empty axis. Always log data[0] before passing it in. If the shape doesn’t match { x: ..., y: ... } style with the exact keys you reference in dataKey, you’re rendering nothing.
Diagnostic Timeline
Chart area is blank in production. The instinct is to wrap everything in <ResponsiveContainer>. Often wrong.
Minute 0–2. Open DevTools → Elements. Find the chart’s outer container. Inspect its computed height. If it’s 0px, the container is the problem and no chart library will render. If it’s a real value (e.g. 400px), the chart logic itself is wrong — keep looking.
Minute 2–5. Container has height. First wrong suspicion: “ResponsiveContainer broke.” Real cause: <XAxis> and <YAxis> missing. Recharts will render an empty SVG without axes because there’s no coordinate system to project data onto. Add <XAxis dataKey="..." /> and <YAxis />. The chart usually appears immediately.
Minute 5–10. Axes present, still nothing. Console.log your data array. If the keys you pass to dataKey don’t match the actual object keys (often a case mismatch: dataKey="Value" vs property value), Recharts silently shows nothing. Recharts does not warn on missing keys. Verify exact string match.
Minute 10–18. Keys match but the line is flat at zero. Real cause: your values are strings, not numbers. JSON from an API often returns "100" instead of 100. Recharts treats strings as NaN and plots them at zero. Coerce: data.map(d => ({ ...d, value: Number(d.value) })).
Minute 18–28. Data shape correct but X-axis is wrong. The most common version of this: dates. Recharts uses string equality for categorical X-axis values. If your data is { date: new Date(...), value: 100 }, the chart treats every Date object as a distinct categorical value because they all toString() differently. Either pre-format with toISOString().slice(0, 10) to get YYYY-MM-DD strings, or use type="number" on <XAxis> and pass timestamps:
<XAxis
dataKey="ts"
type="number"
domain={['dataMin', 'dataMax']}
scale="time"
tickFormatter={(ts) => new Date(ts).toLocaleDateString()}
/>Minute 28+. Last-resort cause: <ResponsiveContainer> height. Now go check the parent. If the parent is display: flex without an explicit height, the container collapses. Wrap the chart in a <div style={{ height: 400 }}> or use height={400} directly on <ResponsiveContainer> instead of height="100%". This is the fix everyone reaches for first; reach for it last so you don’t mask the real issue.
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>.
Date X-axis ticks collapse into one bucket — when <XAxis dataKey="date"> receives Date objects (not strings), each Date is treated as a separate categorical bucket and ticks compress into the first slot. Format dates as ISO strings upstream (new Date(d).toISOString().slice(0, 10)), or set type="number" on the axis and pass Date.now()-style timestamps. The scale="time" plus a tickFormatter lets you keep numeric sorting and human-readable labels.
Tooltip appears far away from the cursor — usually a CSS transform on a parent element. Recharts positions the tooltip in absolute coordinates based on the SVG bounds. If an ancestor uses transform: translateY(...) or is inside a CSS container-type: inline-size, the calculated position is offset. Either remove the transform or use <Tooltip wrapperStyle={{ pointerEvents: 'auto' }} position={{ x, y }} /> to override.
Chart renders correctly on first load but blanks out after a state update — the wrapping <div> may have height: auto and only the first render is sized correctly because of the synchronous layout pass. After a re-render, the parent’s height collapses. Fix with an explicit pixel height on the container or use a ResizeObserver-based wrapper. Recharts’ built-in ResponsiveContainer already uses ResizeObserver, but it needs a parent that can be measured.
For related data visualization issues, see Fix: React useState Not Updating, Fix: TanStack Query Not Working, Fix: React Hook Form Not Working, and Fix: CSS Flexbox 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.