React integration
Render a chart with one component, and reach the chart instance when you need it.
@bloxwap/chart/react exports a <Chart> component. React is an optional peer dependency, so the core package stays dependency-free and only this import needs React.
A chart in one component
import { Chart } from '@bloxwap/chart/react';
import type { Candle } from '@bloxwap/chart';
export function PriceChart({ data }: { data: Candle[] }) {
return <Chart data={data} height={360} />;
}The component is marked 'use client', so it works in the Next.js App Router. It:
- creates the chart after mount and fills its box, following size and device pixel ratio changes;
- calls
setDatawhendatachanges, without recreating the chart; - follows the system color scheme by default, switching live when it changes, and applies
themechanges live; - keeps a bar-close countdown live: while the config shows one (
statusLine.countdownorpriceAxis.labels.countdown, including one turned on later withupdateConfigor the settings card), it runsstartCountdownTickeron the page's timers; - destroys the chart on unmount, which also makes it safe under React Strict Mode.
| Prop | Type | Meaning |
|---|---|---|
data | readonly Candle[] | Candles to display; a new array replaces the data |
theme | 'system' | 'dark' | 'light' | Color theme; default 'system', which follows prefers-color-scheme. With a preset and no theme, none is applied |
preset | 'bloxwapDark' | DeepPartial<ChartConfig> | Brand preset, read once at mount (preset < theme < config) |
config | DeepPartial<ChartConfig> | Initial configuration, applied once at mount |
now | () => number | Clock for the bar-close countdown, read once at mount; default () => Date.now() |
countdownTicker | boolean | Repaint a configured countdown every second, read once at mount; default true. Pass false if you run your own ticker |
height | number | string | Height of the chart box; default 400. The width fills the parent |
className, style | Passed to the chart box | |
onReady | (chart) => void | Called once with the chart instance |
Reach the chart instance
Pass a ref to stream updates, add drawings, or attach the toolbar. onReady runs once, which suits one-time setup like indicators:
import { useEffect, useRef } from 'react';
import { Chart, type ChartInstance } from '@bloxwap/chart/react';
export function LiveChart({ data, feed }) {
const chart = useRef<ChartInstance | null>(null);
// Stream live candles into the current bar.
useEffect(() => feed.subscribe((candle) => chart.current?.appendData(candle)), [feed]);
return (
<Chart
ref={chart}
data={data}
theme="dark"
onReady={(c) => c.addIndicator({ name: 'sma', params: { period: 20 } })}
/>
);
}config, preset, and now are read once at mount; change configuration later through the instance with updateConfig. Attach the toolbar and settings from an effect like the one above, and dispose of their handles in its cleanup.
Presets
<Chart data={data} preset="bloxwapDark" /> // keeps the preset's colors
<Chart data={data} preset="bloxwapDark" theme="light" /> // preset < theme < config
<Chart data={data} theme="dark" now={() => serverNow()} /> // clock for the bar-close countdown
<Chart data={data} config={{ statusLine: { visible: true, countdown: true } }} /> // a live countdown, no ticker codeWith a preset and no theme prop, <Chart> doesn't follow the system color scheme, because a built-in theme would replace the preset's colors. Pass theme="system" to follow it anyway. See theming and presets.
With a paging datafeed
The paging datafeed attaches to the instance. Every new data array replaces the chart's data, so give <Chart> a stable empty array and let the datafeed own the data:
import { useEffect, useState } from 'react';
import { Chart, type ChartInstance } from '@bloxwap/chart/react';
import { attachDatafeed, type Datafeed } from '@bloxwap/chart/datafeed';
import type { Candle } from '@bloxwap/chart';
const NO_DATA: Candle[] = [];
export function MarketChart({ symbol }: { symbol: string }) {
const [chart, setChart] = useState<ChartInstance | null>(null);
const [datafeed, setDatafeed] = useState<Datafeed | null>(null);
useEffect(() => {
if (chart === null) return;
const feed = attachDatafeed(chart, { fetchBars });
setDatafeed(feed);
return () => feed.destroy();
}, [chart]);
useEffect(() => {
void datafeed?.setSymbol(symbol, datafeed.intervalMs ?? 15 * 60_000);
return priceFeed.subscribe(symbol, (mid) => datafeed?.pushTick(mid, symbol));
}, [datafeed, symbol]);
return <Chart data={NO_DATA} preset="bloxwapDark" onReady={setChart} />;
}For a complete bloxwap.pro component with the header, drawing toolbar, and indicators, see migrating from TradingView.
Without the component
If you prefer to own the lifecycle, the core does the sizing for you with autoResize:
'use client';
import { useEffect, useRef } from 'react';
import { createChart, type Candle } from '@bloxwap/chart';
export function PriceChart({ data }: { data: Candle[] }) {
const canvas = useRef<HTMLCanvasElement>(null);
useEffect(() => {
const chart = createChart({ container: canvas.current!, autoResize: true, theme: 'dark', config: { data } });
return () => chart.destroy();
}, [data]);
return <div style={{ height: 400 }}><canvas ref={canvas} /></div>;
}autoResize places the canvas out of flow to fill its parent, so give the parent a height. Because the canvas never contributes to the size being measured, there is no resize feedback loop.