Package Exports
- react-use-echarts
- react-use-echarts/themes/registry
Readme
react-use-echarts
React hooks & component for Apache ECharts — TypeScript, auto-resize, themes, lazy init.
Features
- Hook + Component — use
useEchartshook or the declarative<EChart />component - TypeScript first — complete type definitions with IDE autocomplete
- Zero dependencies — no runtime deps beyond peer deps
- Auto-resize — handles container resizing via ResizeObserver
- Themes — built-in light, dark, and macarons themes, plus any custom theme
- Chart linkage — connect multiple charts for synchronized interactions
- Lazy initialization — defer chart init until element enters viewport
- StrictMode safe — instance cache with reference counting handles double mount/unmount
Requirements
- React 19+ (
react+react-dom) - ECharts 6.x
- Node.js 22+ (required only for tooling/SSR frameworks — the published bundle is browser ESM)
CSR only. ECharts needs a live DOM; SSR is not supported.
ESM-only since 1.3.0. The package publishes a single ESM build (
dist/index.js). Every modern bundler (Vite, Next.js, webpack 5+, Rspack, Parcel, Turbopack) and Node 22+ (require(ESM)) consume it natively. If you still depend on CJS-only tooling, pin to1.2.x.
Installation
npm install react-use-echarts echarts
# or
yarn add react-use-echarts echarts
# or
pnpm add react-use-echarts echartsQuick Start
<EChart /> Component
The simplest way — no ref needed:
import { EChart } from "react-use-echarts";
function MyChart() {
return (
<EChart
option={{
xAxis: { type: "category", data: ["Mon", "Tue", "Wed", "Thu", "Fri"] },
yAxis: { type: "value" },
series: [{ data: [150, 230, 224, 218, 135], type: "line" }],
}}
/>
);
}<EChart /> defaults to width: 100% and height: 100%, so the parent container still needs an explicit height.
Pass ref to access the imperative API — see Returns for the full list (setOption, dispatchAction, clear, resize, appendData, getDataURL, convertToPixel, …).
useEcharts Hook
For full control, use the hook directly:
import { useRef } from "react";
import { useEcharts } from "react-use-echarts";
function MyChart() {
const chartRef = useRef<HTMLDivElement>(null);
const { setOption, getInstance, resize } = useEcharts(chartRef, {
option: { series: [{ type: "line", data: [150, 230, 224, 218, 135] }] },
});
return <div ref={chartRef} style={{ width: "100%", height: "400px" }} />;
}The chart container must have an explicit size, for example style={{ width: "100%", height: "400px" }}.
Recipes
Themes
Built-in themes require one-time registration at app startup:
import { registerBuiltinThemes } from "react-use-echarts/themes/registry";
registerBuiltinThemes();
// Built-in theme
useEcharts(chartRef, { option, theme: "dark" });
// Any string registered via echarts.registerTheme
useEcharts(chartRef, { option, theme: "vintage" });
// Custom theme object (use useMemo to keep reference stable)
const customTheme = useMemo(() => ({ color: ["#fc8452", "#9a60b4", "#ea7ccc"] }), []);
useEcharts(chartRef, { option, theme: customTheme });Event Handling
Supports shorthand (function) and full config (object with query/context):
useEcharts(chartRef, {
option,
onEvents: {
click: (params) => console.log("Clicked:", params),
mouseover: {
handler: (params) => console.log("Hover:", params),
query: "series",
},
},
});Loading State
const [loading, setLoading] = useState(true);
useEcharts(chartRef, {
option,
showLoading: loading,
loadingOption: { text: "Loading..." },
});Chart Linkage
Assign the same group ID — tooltips, highlights, and other interactions will sync:
useEcharts(chartRef1, { option: option1, group: "dashboard" });
useEcharts(chartRef2, { option: option2, group: "dashboard" });Lazy Initialization
Defer chart init until the element scrolls into view:
useEcharts(chartRef, { option, lazyInit: true });
// Custom IntersectionObserver options
useEcharts(chartRef, {
option,
lazyInit: { rootMargin: "200px", threshold: 0.5 },
});Use with Next.js (App Router)
The package entry and themes/registry are marked with "use client", so
importing them inside any React Server Component file does not bundle
ECharts into the server payload. Wrap the chart in your own client
component and import it from any Server Component:
// app/components/MyChart.tsx
"use client";
import { EChart } from "react-use-echarts";
export function MyChart() {
return <EChart option={{ series: [{ type: "line", data: [1, 2, 3] }] }} />;
}// app/page.tsx (Server Component) — imports the Client Component directly
import { MyChart } from "./components/MyChart";
export default function Page() {
return <MyChart />;
}Pages Router only: if you need to load the chart inside
getServerSideProps/getStaticPropspages and force client-only rendering, usedynamic(() => import("./components/MyChart").then((m) => m.MyChart), { ssr: false }). In the App Router,next/dynamicwithssr: falseis disallowed inside Server Components — the"use client"directive already does the right thing.
Gotchas
- Container needs explicit size — the chart won't render in a zero-height div; give the container
height(andwidthif not 100%). - Keep
onEventsreference stable — a newonEventsobject on each render triggers a full rebind. Memoize it withuseMemo(or hoist) when handlers don't change. - Don't share one DOM element across multiple
useEchartshooks — the instance cache reuses a single ECharts instance and emits a dev warning; updates from different hooks will overwrite each other. initOptsand customthemeobjects recreate the instance on reference change — pass memoized or module-level constants unless recreation is intended.- StrictMode is safe — double mount/unmount is handled by the reference-counted instance cache.
API Reference
<EChart /> Props
Declarative component wrapping useEcharts. Accepts all hook options as props plus:
| Prop | Type | Default | Description |
|---|---|---|---|
style |
React.CSSProperties |
{ width: '100%', height: '100%' } |
Container style (merged with defaults) |
className |
string |
— | Container CSS class |
ref |
Ref<UseEchartsReturn> |
— | Exposes the full imperative API (see Returns) |
useEcharts(ref, options)
Options
| Option | Type | Default | Description |
|---|---|---|---|
option |
EChartsOption |
(required) | ECharts configuration |
theme |
string | object |
— | Any registered theme name, or custom theme object |
renderer |
'canvas' | 'svg' |
'canvas' |
Renderer type |
lazyInit |
boolean | IntersectionObserverInit |
false |
Lazy initialization via IntersectionObserver |
group |
string |
— | Chart linkage group ID |
setOptionOpts |
SetOptionOpts |
— | Default options for setOption calls |
showLoading |
boolean |
false |
Show loading indicator |
loadingOption |
object |
— | Loading indicator configuration |
onEvents |
EChartsEvents |
— | Event handlers (fn or { handler, query?, context? }) |
autoResize |
boolean |
true |
Auto-resize via ResizeObserver |
initOpts |
EChartsInitOpts |
— | Passed to echarts.init() (devicePixelRatio, locale, width, etc.) |
onError |
(error: unknown) => void |
— | Error handler — effect failures logged via console.error without it; imperative setOption throws without it |
Returns
Prefer the declarative props (
option,theme,showLoading, …) over imperative methods. Use these methods only when a prop does not cover the action — image export, coordinate conversion, streaming append, etc. All methods are no-ops or return safe defaults when the instance is not yet initialized. When the instance throws, errors are routed throughonErrorif provided (and the call returns the fallback); otherwise the error is rethrown — including from readers (noconsole.errorfallback for imperative methods).
Lifecycle / updates
| Method | Type | Description |
|---|---|---|
setOption |
(option: EChartsOption, opts?: SetOptionOpts) => void |
Update chart configuration |
dispatchAction |
(payload: Payload, opt?: boolean | { silent?: boolean; flush?: boolean }) => void |
Dispatch an ECharts action (highlight, downplay, showTip, etc.) |
clear |
() => void |
Clear current chart content |
resize |
(opts?: ResizeOpts) => void |
Manually trigger chart resize. ResizeOpts accepts width/height/animation/silent |
appendData |
(params: { seriesIndex: number; data: ArrayLike<unknown> }) => void |
Append data to a series (streaming). Drift-aware: drops dedup memory so a subsequent shallow-equal-but-new-ref option rerender re-applies setOption |
Read / introspect
| Method | Type | Description |
|---|---|---|
getInstance |
() => ECharts | undefined |
Get ECharts instance |
getOption |
() => EChartsOption | undefined |
Get the current merged option |
getWidth |
() => number | undefined |
Container width in pixels |
getHeight |
() => number | undefined |
Container height in pixels |
getDom |
() => HTMLElement | undefined |
Underlying DOM container |
isDisposed |
() => boolean |
Whether the instance is disposed (returns true when uninitialized — semantically gone) |
Export
| Method | Type | Description |
|---|---|---|
getDataURL |
(opts?) => string | undefined |
Base64 image data URL (png / jpeg / svg) |
getConnectedDataURL |
(opts?) => string | undefined |
Combined image of all charts in the same group |
renderToSVGString |
(opts?: { useViewBox?: boolean }) => string | undefined |
Render chart to SVG string (works with the SVG renderer) |
getSvgDataURL |
() => string | undefined |
Get SVG data URL of the current chart |
Coordinate conversion
| Method | Type | Description |
|---|---|---|
convertToPixel |
(finder: ChartFinder, value: ChartScaleValue | ChartScaleValue[]) => number | number[] | undefined |
Logical → pixel coordinates |
convertFromPixel |
(finder: ChartFinder, value: number | number[]) => number | number[] | undefined |
Pixel → logical coordinates |
containPixel |
(finder: ChartFinder, value: number[]) => boolean |
Whether a pixel point is inside the matched component (false when uninit) |
ChartFinder is string | { seriesIndex?, seriesId?, …, geoIndex?, … } — a string shorthand or a model finder object. ChartScaleValue is number | string | Date.
Other Exports
import { useLazyInit } from "react-use-echarts"; // standalone lazy init hook
import { isBuiltinTheme, registerCustomTheme } from "react-use-echarts"; // theme utils (no JSON)
import { registerBuiltinThemes } from "react-use-echarts/themes/registry"; // ~20KB theme JSON
// All exported types: UseEchartsOptions, UseEchartsReturn, EChartProps,
// EChartsEvents, EChartsEventConfig, EChartsInitOpts, BuiltinTheme, LoadingOption,
// ChartFinder, ChartScaleValue
// EChartsOption, SetOptionOpts, ResizeOpts come from the "echarts" package directly.Migrating from echarts-for-react
Most props map 1:1; a few are folded into existing options. Quick reference:
echarts-for-react |
react-use-echarts |
Notes |
|---|---|---|
option |
option |
Same |
theme |
theme |
Same; built-in themes need registerBuiltinThemes() first (see Themes) |
notMerge / lazyUpdate |
setOptionOpts: { notMerge, lazyUpdate } |
Folded into a single object passed to setOption |
showLoading |
showLoading |
Same |
loadingOption |
loadingOption |
Same |
onEvents |
onEvents |
Same shape; also accepts { handler, query?, context? } for query/context binding |
onChartReady |
Use the imperative API | Read getInstance() from the hook return (or ref.current) — fires after first init |
opts.renderer |
renderer: 'canvas' | 'svg' |
Promoted to a top-level option |
opts (rest) |
initOpts |
Same shape (devicePixelRatio, locale, width, height, useDirtyRect, etc.) |
style |
style |
<EChart /> defaults to { width: '100%', height: '100%' } so the parent needs size |
className |
className |
Same |
lazyUpdate (top-level) |
setOptionOpts: { lazyUpdate: true } |
See notMerge row |
shouldSetOption |
Gate the option prop yourself |
Top-level keys are deduped via shallowEqual automatically; for custom predicates (deep compare, throttling, app-state gating) memoize/skip the option prop in the parent component |
autoResize (4.x) |
autoResize |
Same default (true); resize uses ResizeObserver + RAF |
| none | lazyInit |
New: defer init until the container scrolls into viewport |
| none | group |
New: chart linkage via shared group ID |
| none | onError |
New: route init / setOption / dispatchAction errors through a callback |
Side-by-side example:
// echarts-for-react
<ReactECharts
option={option}
theme="dark"
notMerge
lazyUpdate
opts={{ renderer: "svg", devicePixelRatio: 2 }}
onEvents={{ click: handleClick }}
showLoading={loading}
onChartReady={(instance) => instanceRef.current = instance}
/>
// react-use-echarts
<EChart
ref={chartRef}
option={option}
theme="dark"
setOptionOpts={{ notMerge: true, lazyUpdate: true }}
renderer="svg"
initOpts={{ devicePixelRatio: 2 }}
onEvents={{ click: handleClick }}
showLoading={loading}
/>
// chartRef.current?.getInstance() replaces onChartReadyContributing
We welcome all contributions. Please read the contributing guidelines first.
Changelog
Detailed changes for each release are documented in the release notes.