echarts-for-react: The Complete Guide to Building Interactive React Charts with Apache ECharts
If you’ve ever tried to wire up Apache ECharts directly into a React component without a wrapper, you know the pain: manual DOM references, lifecycle juggling, and a gnawing feeling that something is about to break on re-render. echarts-for-react exists precisely to spare you that experience. It’s a thin, well-designed React wrapper around ECharts that turns the entire chart configuration story into a single declarative component — and it does so without hiding ECharts’ raw power from you.
This guide covers everything from echarts-for-react installation and basic setup through event handling, dynamic data updates, theming, TypeScript support, and dashboard composition. Whether you’re building a one-off sparkline or a full React ECharts dashboard, you’ll leave here knowing exactly what to reach for and why.
Why echarts-for-react — and Not Something Else?
The React chart library ecosystem is crowded. Recharts, Victory, Nivo, Chart.js wrappers, Visx — they all have opinions, and most of those opinions involve reimplementing rendering logic that ECharts already handles brilliantly. Apache ECharts is one of the most capable charting engines available: it renders via Canvas or SVG, handles millions of data points with grace, ships with 20+ chart types out of the box, and has a theming system sophisticated enough to make a designer smile. The only thing it lacks natively is idiomatic React integration.
echarts-for-react bridges that gap with minimal ceremony. The library weighs almost nothing on its own — its job is lifecycle management, not chart logic. It mounts the ECharts instance when the component mounts, updates options when props change, disposes the instance on unmount, and exposes the raw instance whenever you need it. That’s it. You write ECharts option objects exactly as you would in vanilla JS; React state drives the data. The mental model is simple and the escape hatches are clean.
The practical consequence: you get full access to every ECharts feature — including the ones that third-party wrappers often paper over. Tooltip formatters, rich data zoom controls, brush selection, GL extensions, custom series — all available, all configured exactly as the ECharts documentation describes them. If ECharts can do it, React Apache ECharts via this wrapper can do it too.
Installation and Project Setup
echarts-for-react installation requires two packages: the core engine and the wrapper. Both must be present — the wrapper declares echarts as a peer dependency rather than bundling it, which keeps your bundle size under your control and avoids version conflicts if you’re already using ECharts elsewhere in the project.
# npm
npm install echarts echarts-for-react
# yarn
yarn add echarts echarts-for-react
# pnpm
pnpm add echarts echarts-for-react
Once installed, the echarts-for-react setup in a component is three lines of meaningful code. Import ReactECharts, define your option object (the same structure you’d pass to echartsInstance.setOption()), and render the component. The chart fills its container by default, so wrapping it in a sized div is the only layout concern you need to think about.
import ReactECharts from 'echarts-for-react';
const option = {
tooltip: { trigger: 'axis' },
xAxis: {
type: 'category',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
},
yAxis: { type: 'value' },
series: [
{
name: 'Weekly Sales',
type: 'line',
smooth: true,
data: [820, 932, 901, 934, 1290, 1330, 1320],
},
],
};
export default function SalesChart() {
return (
<div style={{ width: '100%', height: 400 }}>
<ReactECharts option={option} />
</div>
);
}
That renders a fully interactive, animated line chart. Tooltips, legend toggling, and zoom are all wired up by the option configuration — no event handlers needed at this stage. For most straightforward React data visualization use cases, this is genuinely all the code required to go from zero to a production-ready chart.
import * as echarts from 'echarts' with individual component imports, then pass the custom echarts object to ReactECharts via the echarts prop.
Core Props You’ll Actually Use
The ReactECharts component accepts a handful of props that cover the full range of interaction patterns. Understanding them upfront saves you from reaching for workarounds that aren’t necessary. The option prop is the primary data channel — it accepts the complete ECharts configuration object and triggers a re-render whenever it changes (React’s normal diffing applies). The style and className props control the wrapper element’s appearance.
The notMerge prop controls how ECharts handles option updates. By default (notMerge={false}), new options are merged with the existing state — useful when you’re updating a subset of the configuration. Set notMerge={true} when you want a clean replacement, such as switching between entirely different chart types. The lazyUpdate prop defers the DOM update until the next animation frame, which can smooth out rapid state changes in data-heavy dashboards.
The theme prop accepts either a registered theme name string or an inline theme object. ECharts ships with a handful of built-in themes, and the community maintains an extensive library of custom ones — you can build your own at the ECharts Theme Builder and pass the JSON output directly. This makes brand-consistent echarts-for-react customization entirely painless without modifying any series-level config.
Dynamic Data: Driving Charts with React State
The idiomatic way to update a React chart component is exactly what you’d expect from React: put the data in state, derive the option object from that state, and let the component re-render. ReactECharts detects the changed option prop and calls setOption internally. ECharts’ built-in transition animations handle the visual update smoothly — no manual animation code, no imperative chart API calls needed for the common case.
import { useState, useCallback } from 'react';
import ReactECharts from 'echarts-for-react';
const datasets = {
weekly: [820, 932, 901, 934, 1290, 1330, 1320],
monthly: [3200, 4100, 3800, 5200, 4700, 5900, 6100],
};
export default function DynamicChart() {
const [range, setRange] = useState<'weekly' | 'monthly'>('weekly');
const option = {
xAxis: {
type: 'category',
data: range === 'weekly'
? ['Mon','Tue','Wed','Thu','Fri','Sat','Sun']
: ['Jan','Feb','Mar','Apr','May','Jun','Jul'],
},
yAxis: { type: 'value' },
series: [{ type: 'bar', data: datasets[range], name: 'Revenue' }],
tooltip: { trigger: 'axis' },
};
return (
<div>
<button onClick={() => setRange('weekly')}>Weekly</button>
<button onClick={() => setRange('monthly')}>Monthly</button>
<ReactECharts option={option} notMerge={true} />
</div>
);
}
One nuance worth noting: because the option object is reconstructed on every render, you should either define it outside the component (if it’s static) or memoize it with useMemo (if it depends on state). Without memoization, the object reference changes on every render even when the data hasn’t, which causes unnecessary ECharts setOption calls. In practice this rarely causes visible issues, but it’s a clean habit that prevents subtle performance problems in data-dense dashboards.
For real-time data — think live metrics, WebSocket feeds, or polling — the same pattern scales without modification. Update state on each data arrival, let React schedule the re-render, and ECharts handles the animation. If you’re appending to a large dataset and performance becomes a concern, look at ECharts’ appendData API accessed via the chart instance (covered in the next section), which bypasses React’s rendering cycle entirely for the data layer.
Event Handling and Accessing the Chart Instance
echarts-for-react events are handled through the onEvents prop, which accepts a plain object mapping ECharts event names to handler functions. The handler receives the event parameters that ECharts would normally pass to a listener registered via chart.on() — series data, data index, component type, and so on. This covers the full ECharts event vocabulary: click, dblclick, mouseover, mouseout, legendselectchanged, datazoom, brushselected, and more.
const onEvents = {
click: (params) => {
console.log('Clicked series:', params.seriesName);
console.log('Data value:', params.value);
console.log('Data index:', params.dataIndex);
},
legendselectchanged: (params) => {
console.log('Legend selection changed:', params.selected);
},
};
<ReactECharts option={option} onEvents={onEvents} />
When you need direct access to the ECharts instance — for imperative operations like showLoading, hideLoading, dispatchAction, or appendData — use the onChartReady callback or a ref. The onChartReady prop fires once when the chart is initialized and passes the raw ECharts instance. Alternatively, attach a ref to the ReactECharts component and call ref.current.getEchartsInstance() to retrieve the instance at any point after mount.
import { useRef } from 'react';
import ReactECharts from 'echarts-for-react';
export default function ControlledChart() {
const chartRef = useRef(null);
const showLoading = () => {
const instance = chartRef.current?.getEchartsInstance();
instance?.showLoading();
setTimeout(() => instance?.hideLoading(), 2000);
};
return (
<div>
<button onClick={showLoading}>Simulate Loading</button>
<ReactECharts ref={chartRef} option={option} />
</div>
);
}
A common pattern in complex dashboards is using dispatchAction to programmatically highlight series, trigger tooltips, or simulate legend selections in response to interactions elsewhere on the page. Because you have the raw instance, everything in the ECharts API documentation is available — the wrapper doesn’t wrap you into a corner. This is, candidly, one of the most important design decisions the library makes.
Customization: Themes, Styles, and Responsive Behaviour
echarts-for-react customization operates at three levels. The first is the option object itself — ECharts exposes every visual property (colors, fonts, padding, label formatters, tooltip HTML) through the option API, and all of it works as documented. The second level is theming: register a theme globally with echarts.registerTheme('my-theme', themeObject) and then pass theme="my-theme" to any ReactECharts instance. The third level is direct instance manipulation, useful for edge cases that neither the option nor the theme system covers.
Responsive chart sizing in React is handled by a combination of ECharts’ internal resize listener and the container’s CSS. The ReactECharts component passes a style prop directly to the wrapper div — set width: '100%' and a fixed or percentage height there. ECharts automatically resizes the canvas when the window resizes. For more granular control (e.g., responding to a sidebar collapsing), call instance.resize() manually on the relevant DOM event. In a CSS Grid or Flexbox layout, percentage widths work reliably without any additional setup.
For dark mode support, the cleanest approach is to maintain two theme objects — light and dark — and swap the theme prop based on a context value or a CSS class on the root element. ECharts re-renders with the new theme on the next update. If your application already uses a design token system, you can generate the theme object programmatically from your token values, ensuring that the charts stay in sync with the rest of the UI without manual duplication.
Building a React ECharts Dashboard
A React ECharts dashboard is, structurally, just several ReactECharts instances sharing state through a common parent or a state management layer. Each chart gets its own option, derived from the same underlying data store. Because ECharts instances are isolated, they don’t interfere with each other — you can mix chart types freely, and each responds to its own events independently.
Cross-chart interaction — clicking a bar in one chart to filter data in another — is a straightforward React state problem. The onEvents handler on the source chart updates shared state (via useState, Zustand, Redux, or whatever you prefer); the downstream charts derive their options from that updated state and re-render. No special echarts-for-react wiring is needed for this; it’s just React data flow.
// Simplified cross-chart filtering
const [selectedCategory, setSelectedCategory] = useState(null);
const barEvents = {
click: (params) => setSelectedCategory(params.name),
};
const filteredLineOption = {
// ...derive from selectedCategory
series: [{
data: selectedCategory
? fullData.filter(d => d.category === selectedCategory).map(d => d.value)
: fullData.map(d => d.value),
type: 'line',
}],
};
return (
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 24 }}>
<ReactECharts option={barOption} onEvents={barEvents} />
<ReactECharts option={filteredLineOption} />
</div>
);
Performance considerations become relevant when you’re rendering six or more charts simultaneously with live data. The main levers are: memoize option objects with useMemo; use lazyUpdate={true} on charts that don’t need immediate visual feedback; consider React.memo on chart wrapper components; and, for truly high-frequency updates (sub-100ms), bypass React state entirely for the data layer and push updates directly via instance.setOption() or instance.appendData(). The last approach trades declarative clarity for raw speed, so use it selectively.
TypeScript Support and Tree-Shaking
echarts-for-react ships with TypeScript declarations. The EChartsOption type from the core echarts package provides full autocomplete for option objects — a genuinely useful authoring experience given how large the ECharts option API is. The ReactECharts component’s props are typed through EChartsReactProps, exported from the package. For event handler params, ECharts exports ECElementEvent and related types.
import ReactECharts from 'echarts-for-react';
import type { EChartsOption } from 'echarts';
const option: EChartsOption = {
xAxis: { type: 'category', data: ['A', 'B', 'C'] },
yAxis: { type: 'value' },
series: [{ type: 'bar', data: [10, 20, 30] }],
};
export default function TypedChart() {
return <ReactECharts option={option} style={{ height: 300 }} />;
}
For bundle optimization, ECharts 5 supports a tree-shakeable import pattern where you import only the renderers, charts, and components you actually use. The resulting bundle can be dramatically smaller than importing the full library — relevant if you’re shipping to mobile or performance-constrained environments. Pass your custom-built ECharts object to ReactECharts via the echarts prop:
import * as echarts from 'echarts/core';
import { BarChart } from 'echarts/charts';
import { GridComponent, TooltipComponent } from 'echarts/components';
import { CanvasRenderer } from 'echarts/renderers';
import ReactECharts from 'echarts-for-react';
echarts.use([BarChart, GridComponent, TooltipComponent, CanvasRenderer]);
export default function LightweightChart() {
return (
<ReactECharts
echarts={echarts}
option={{ /* ... */ }}
/>
);
}
This pattern is particularly valuable in micro-frontend architectures or Next.js applications where each page bundle is independently optimized. With selective imports, a chart page that only needs bar and line charts can shave 60–70% off the ECharts footprint compared to the full build. The echarts prop on ReactECharts makes this opt-in with no structural changes to the rest of your code.
Common Patterns, Pitfalls, and Practical Tips
A few things trip up most developers on first contact. The most common: forgetting that the chart container needs an explicit height. ECharts renders into a canvas element sized to its parent; if the parent has height: auto and no content forcing a height, the chart renders at zero pixels. Always set a height on the wrapper div or via the style prop — style={{ height: 400 }} is the fastest fix.
- Stale closures in event handlers: If your
onEventsobject is defined inside the component and references state, ensure the option and onEvents objects update together — or useuseCallbackwith appropriate dependencies. - Multiple series updates: When adding or removing series dynamically, use
notMerge={true}to avoid ghost series from previous renders bleeding through the merge. - SSR environments (Next.js): ECharts is browser-only. Use dynamic imports with
ssr: falseor guard the import inside auseEffectto prevent server-side rendering errors. - Tooltip HTML content: ECharts tooltip formatters support returning HTML strings. This works fine in Canvas mode but requires
useHTML: falseconsideration in SVG mode. Test both renderers if you need flexibility.
The echarts-for-react getting started experience is intentionally smooth, but production usage rewards understanding ECharts’ option model deeply. The official ECharts documentation is comprehensive and well-maintained — when a chart isn’t behaving as expected, the answer is almost always in the option API reference rather than in the wrapper itself. The wrapper’s surface area is small by design; most questions are ECharts questions, not echarts-for-react questions.
For a detailed walkthrough with additional echarts-for-react examples and code patterns, the tutorial at StackForgeDev on Dev.to covers practical implementations including multi-axis charts, gradient fills, and custom tooltip components. It’s a solid complement to the official docs if you prefer learning through runnable examples.
echarts-for-react vs. Alternatives: An Honest Take
Recharts is the most common comparison point. It’s React-native by design — SVG-based, component-oriented, and extremely approachable for simple charts. It shines for standard bar/line/pie use cases where the defaults look good and customization needs are modest. It starts to struggle with large datasets, complex interactions, and chart types beyond its component library. ECharts handles all of those cases natively, which is why teams that start with Recharts often migrate to echarts-for-react as their requirements grow.
Chart.js wrappers (react-chartjs-2) sit in a similar position: solid for common chart types, Canvas-based (good for performance), but with a configuration model that becomes cumbersome for complex needs. Nivo is beautiful and highly customizable within its abstraction layer, but that layer can become a ceiling when you need something it doesn’t expose. Victory is clean and well-documented but shows performance limitations at scale.
The honest answer is: echarts-for-react is the right choice when you need ECharts’ feature depth, performance, or specific chart types (heatmaps, treemaps, parallel coordinates, 3D charts via ECharts GL) inside a React application. It’s overkill if you need one simple bar chart and nothing more. For everything in between, the declarative option model and clean React integration make it one of the most capable React interactive charts solutions available — and the relatively small wrapper footprint means you’re betting on Apache ECharts (a well-funded, actively maintained Apache Software Foundation project) rather than on a third-party abstraction layer.
Frequently Asked Questions
How do I install and set up echarts-for-react in a React project?
Run npm install echarts echarts-for-react to install both the core engine and the React wrapper. Then import ReactECharts from 'echarts-for-react' in your component, define an ECharts option object, and pass it as a prop. Make sure the wrapper element has an explicit height — without it, the canvas renders at zero pixels. That’s the complete setup for a working chart.
How do I handle events and access the chart instance in echarts-for-react?
Pass an onEvents prop to ReactECharts — it’s a plain object mapping ECharts event names (e.g., 'click', 'datazoom') to handler functions. Each handler receives the standard ECharts event parameter object. To access the raw ECharts instance for imperative operations, attach a ref to the component and call ref.current.getEchartsInstance(), or use the onChartReady callback prop which fires once on initialization.
How do I update chart data dynamically in echarts-for-react?
Store your data in React state with useState, derive the ECharts option object from that state, and pass it to ReactECharts. When the state updates, the component re-renders with the new option, and ECharts applies the changes with its built-in animations. For performance, memoize the option object with useMemo to avoid unnecessary setOption calls. For very high-frequency updates, access the chart instance directly and call instance.setOption() outside React’s rendering cycle.






