React components
A thin React wrapper around every chart class below — same config, same events, same
imperative methods, exposed as a component instead of new XChart(el, config). Ships in
the same package, under a separate entry point so plain-TS consumers pull in no
React code.
Install
# same package as the vanilla charts — react / react-dom are peer deps
npm install @smalldat/sandycoast react react-dom
React 18 and 19 are both supported (peerDependencies). If your app doesn't already
depend on React, the base @smalldat/sandycoast import stays React-free — only code that
imports from @smalldat/sandycoast/react pulls it in.
Quick start
import { BarChart } from '@smalldat/sandycoast/react';
const data = {
points: [
{ x: 'Q1', y: 120, z: 'EU' },
{ x: 'Q1', y: 90, z: 'US' },
{ x: 'Q2', y: 140, z: 'EU' },
],
};
// hoisted, so its identity is stable across renders — see below
const options = { grain: { sizePx: 2.4 }, legend: { show: true } };
function Revenue() {
return (
<BarChart
data={data}
options={options}
style={{ width: '100%', height: 360 }}
onHover={({ bar }) => console.log(bar?.xValue, bar?.yValue)}
/>
);
}
ResizeObserver, so give it a size — pass style (as above) or a
className with a CSS rule.
Shared prop shape
Every component below takes the same five kinds of prop. Chart-specific pieces are the data type,
the options type (that chart's config, minus data), and the event props —
see Components.
| Prop | Type | Description |
|---|---|---|
data required | DataSet | MeshDataSet | OhlcDataSet | WindDataSet | That chart's data — the same shape the vanilla constructor takes. See Data model. |
options | Omit<XChartConfig, 'data'> | Every other config field that chart's doc page lists (style, animation, chrome, pan/zoom, …). |
className / style | string / CSSProperties | Applied to the chart's container <div>. |
onXxx | (payload) => void | One prop per event that chart's .on(event, fn) supports — see Components for the list per chart. |
ref | Ref<XChart> | The underlying vanilla chart instance once mounted. See below. |
data vs. options — what re-renders as what
The two props are deliberately handled differently, matching what the vanilla API can do efficiently:
| Prop change | What happens |
|---|---|
data changes (new reference) |
Calls the chart's update(data) (or setData for the wind rose) — grains morph to the new targets, no re-pour. |
options changes (new reference) |
The chart is disposed and recreated — config like style, animation timing or axes isn't dynamically patchable on the vanilla class either, so this mirrors it exactly. |
options. An inline object literal is a new reference every
render, which would recreate the chart on every re-render. Hoist it to module scope (as in the quick
start above) or wrap it in useMemo. The same goes for data, for a milder
reason: a fresh literal every render restarts the morph tween rather than recreating the chart.
Components
| Component | data type | Event props | Config reference |
|---|---|---|---|
BarChart | DataSet | onHover, onSeriesFocus | Bar chart → |
LineChart | DataSet | onHover, onSeriesFocus | Line chart → |
PieChart | DataSet | onHover, onSeriesChange, onSeriesFocus | Pie chart → |
ScatterChart | MeshDataSet | onHover, onSeriesFocus | Scatter chart → |
WindRoseChart<Custom> | WindDataSet<Custom> | onHover, onSelect, onHighlight | Wind rose → |
CandlestickChart | OhlcDataSet | onHover, onClick, onSeriesChange, onSeriesFocus | Candlestick chart → |
Each event prop's payload is exactly what the matching vanilla .on(event, fn) passes —
see that chart's own Events section for the payload shape.
WindRoseChart is generic over the same Custom per-observation payload type
as the vanilla class; it defaults to unknown.
The ref — imperative access
A ref on any of these components resolves to the underlying chart instance — the exact
same object new XChart(el, config) would give you — once it has mounted (null
before that). Use it for anything that isn't a prop: focusSeries(),
getView()/panBy()/zoomTo(), repour(),
getData(), the wind rose's selectObservation() / selectSegment(),
and so on — see each chart's Methods section.
import { useRef } from 'react';
import { BarChart } from '@smalldat/sandycoast/react';
import type { BarChart as BarChartInstance } from '@smalldat/sandycoast';
function Chart() {
const ref = useRef<BarChartInstance>(null);
return (
<>
<button onClick={() => ref.current?.focusSeries(null)}>Clear isolation</button>
<BarChart ref={ref} data={data} style={{ height: 360 }} />
</>
);
}
More examples
Live data — the same morph the vanilla API gives you
function Live() {
const [data, setData] = useState(initialData);
// A new `data` reference morphs the grains in place — no re-pour.
const refresh = () => setData(fetchLatest());
return <BarChart data={data} style={{ height: 360 }} />;
}
Wind rose with a typed Custom payload
JSX doesn't take an explicit generic argument on the tag, so type windData itself —
WindRoseChart infers Custom from it.
interface Reading { stationId: string; }
const windData: WindDataSet<Reading> = { points: [/* … */] };
<WindRoseChart
data={windData}
onSelect={({ segment, observationId }) => console.log(segment?.key, observationId)}
/>
© 2026 · License · Data model →