# React

> Declarative components and hooks over the same imperative API.

Section: Packages. Source: https://ortexcharts.com/docs/react

## What the Package Is

`@ortex-charts/react` is a thin binding layer with no logic of its own. Every component
creates the corresponding imperative object in a layout effect, applies prop changes with
`applyOptions` or `setData`, and disposes it on unmount. The underlying handle is always
available through `ref`, so nothing is lost by using the components.

It is tested against React 17, 18 and 19, and declares them as peer dependencies with
`>=17`.

```bash
npm install @ortex-charts/react
```

_Live example: [react-chart](https://ortexcharts.com/examples/react-chart)_

## The Components

| Component | Wraps | Handle through `ref` |
|---|---|---|
| `<Chart>` | `createChart` | `Chart` |
| `<Pane>` | `chart.addPane` | — (provides its id through context) |
| `<Series>` | `chart.addSeries` | `SeriesModel` |
| `<FinancialChart>` | `createFinancialChart` | `FinancialChart` |
| `<ChartShell>` | `createChartShell` | `ChartShell` |
| `<Sparkline>` | `createSparkline` | `Sparkline` |

## The Financial Chart

The common case is one component with data, indicators and markers as props.

```tsx
import { FinancialChart, type IndicatorSpec } from "@ortex-charts/react";
import type { Bar, SeriesMarker } from "@ortex-charts/react";
import { useMemo, useState } from "react";

const OPTIONS = { theme: "dark", timeZone: "America/New_York", resolution: "1D" } as const;

export function PriceChart({ bars, earnings }: { bars: Bar[]; earnings: SeriesMarker[] }) {
  const [hover, setHover] = useState("");

  const indicators = useMemo<IndicatorSpec[]>(
    () => [
      { id: "ema", inputs: { length: 21 } },
      { id: "sma", inputs: { length: 200 } },
      { id: "rsi" },
    ],
    [],
  );

  return (
    <>
      <FinancialChart
        options={OPTIONS}
        data={bars}
        indicators={indicators}
        markers={earnings}
        style={{ height: 460 }}
        onCrosshairMove={(e) => setHover(e.time ? new Date(e.time).toDateString() : "")}
      />
      <p>{hover}</p>
    </>
  );
}
```

`indicators` is diffed by identity: new entries are added, missing ones removed, and the
rest are left alone rather than being torn down and recreated. The identity of an entry is
the JSON of `{ id, inputs, options }`, or an explicit `key` when you supply one, so two
moving averages with different lengths are two distinct entries and an unchanged array
causes no work at all.

`data` and `markers` are applied whenever the **array identity** changes, so a new array on
every render means `setData` on every render. Memoize them or hold them in state.

`options` is compared shallowly at the top level and the changed keys go to
`applyOptions`. Two keys — `seriesType` and `volume` — take effect only at creation, because
they decide what the chart is made of.

## The Core Chart With Children

For anything that is not a standard price chart, compose it.

```tsx
import { Chart, Pane, Series } from "@ortex-charts/react";

<Chart options={{ theme: "light", timeZone: "UTC" }} style={{ height: 520 }}>
  <Series type="candlestick" data={bars} options={{ title: "AAPL" }} />

  <Pane heightRatio={0.2}>
    <Series type="histogram" data={volume} options={{ title: "Volume", colorMode: "mainDirection" }} />
  </Pane>

  <Pane heightRatio={0.25}>
    <Series type="line" data={shortInterest} options={{ title: "Short interest", align: "forwardFill" }} />
  </Pane>
</Chart>
```

`<Series>` renders nothing into the DOM; it manages a series on the chart from context.
`<Pane>` provides its id to nested series, and `options.pane` on a series overrides the
enclosing pane if you need to place one explicitly.

Changing `type` on a `<Series>` recreates it. Changing `heightRatio` or `minHeight` on a
`<Pane>` applies live.

## The Full Shell

```tsx
import { ChartShell } from "@ortex-charts/react";

<ChartShell
  symbol={symbol}
  resolution={resolution}
  layout={savedLayout}
  theme={theme}
  options={{
    datafeed,
    overlays,
    toolbar: { download: true },
    onLayoutChange: saveLayout,
    onSymbolChange: (s) => router.push(`/chart/${s}`),
  }}
  style={{ height: 640 }}
/>
```

`symbol`, `resolution` and `theme` are controlled props: the shell follows them when they
change, so the chart and your application state stay in step in either direction.

`layout` is re-applied whenever its identity changes, which is what you want after loading a
user's saved layout asynchronously.

`options` is read at creation. Callbacks inside it are always invoked through their latest
value, so inline arrow functions are fine and do not need memoizing. Structural options —
`datafeed`, `overlays`, `toolbar` — need a remount to change, and the idiomatic way to force
one is a `key`:

```tsx
<ChartShell key={pageKind} options={{ datafeed, overlays: overlaysFor(pageKind) }} />
```

## Hooks

### `useChart` and `usePaneId`

Inside a `<Chart>` or `<FinancialChart>`, `useChart()` returns the core `Chart` or `null`
until it exists after the first commit. `usePaneId()` returns the id of the enclosing
`<Pane>`. Both are how you write your own child components.

```tsx
import { useChart, usePaneId } from "@ortex-charts/react";
import { useEffect } from "react";

function SessionShading() {
  const chart = useChart();
  const paneId = usePaneId();

  useEffect(() => {
    if (!chart) return;
    const primitive = makeSessionShading();
    chart.addPrimitive(primitive, paneId);
    return () => chart.removePrimitive(primitive.id);
  }, [chart, paneId]);

  return null;
}
```

### `useChartEvents`

The four event props — `onCrosshairMove`, `onClick`, `onDblClick`, `onVisibleRangeChange` —
are accepted directly by `<Chart>` and `<FinancialChart>`. The hook behind them is exported
for a chart you hold yourself:

```tsx
useChartEvents(chart, { onCrosshairMove: setHover, onVisibleRangeChange: setRange });
```

Subscriptions are made once per chart instance and always call the latest handler, so
handlers never need memoizing.

### `useLiveSource`

A `LiveSource` must survive re-renders and be closed exactly once. `useMemo` does not
guarantee either. `useLiveSource` does: it keeps the value for the lifetime of its
dependencies, closes the previous one when they change, and closes the last one on unmount.

```tsx
import { FinancialChart, useLiveSource } from "@ortex-charts/react";
import { websocketSource, type LiveItem } from "@ortex-charts/financial";

function LiveChart({ symbol, bars }: { symbol: string; bars: Bar[] }) {
  const source = useLiveSource(
    () =>
      websocketSource<LiveItem>({
        url: `wss://feed.example.com/${symbol}`,
        parse: (raw) => JSON.parse(String(raw)) as LiveItem,
        onOpen: (send) => send(JSON.stringify({ subscribe: symbol })),
      }),
    [symbol],
  );

  return <FinancialChart data={bars} live={source} options={{ resolution: "1" }} style={{ height: 420 }} />;
}
```

Anything with a `close()` method is closed, so the hook works for your own sources too.

## Sparklines

```tsx
import { Sparkline } from "@ortex-charts/react";

<Sparkline data={closes} width={120} height={28} options={{ kind: "area", colorByTrend: true }} />
```

The container is an inline-block `span` with zero line height, so it sits on the text
baseline. Hold a `ref` to call `push` for live values. See [Sparklines](https://ortexcharts.com/docs/sparklines).

## Referential Stability

This is the one thing to get right, and it applies to every component in the package.

`options` and `data` props are compared by identity, not by value. A new object literal on
every render is a new identity, so the effect runs every render.

```tsx
// Wrong: a new object every render, so applyOptions runs every render.
<FinancialChart options={{ theme: "dark", resolution: "1D" }} data={bars.map(toBar)} />

// Right: stable identities.
const OPTIONS = { theme: "dark", resolution: "1D" } as const;   // module constant
const data = useMemo(() => rows.map(toBar), [rows]);            // memoized

<FinancialChart options={OPTIONS} data={data} />
```

Callbacks are the exception. Every event prop and every shell callback is routed through a
ref, so inline functions are correct and cost nothing.

## Getting the Imperative Handle

```tsx
import { useRef } from "react";
import { FinancialChart, type FinancialChartHandle } from "@ortex-charts/react";

const ref = useRef<FinancialChartHandle | null>(null);

<FinancialChart ref={ref} data={bars} onReady={(c) => c.chart.timeScale.fitContent()} />;

// Later:
ref.current?.chart.timeScale.setVisibleTimeRange(from, to);
ref.current?.alerts().add({ price: 182.5, label: "Breakout" });
```

`onReady` fires once with the handle after creation, which is the right place for anything
that has to happen before the user sees the first frame. The `ref` is the right place for
anything that happens later.

For realtime updates, prefer the handle over the `data` prop: `ref.current?.update(bar)` is
one row, while a new `data` array is a full `setData`.

## Server-Side Rendering

The components check for `window` before creating anything, so they render an empty
container on the server and fill it on hydration. In Next.js the file still needs
`"use client"`, because the effect only runs in a client component. There is no
server-rendered chart image; see [Installation and delivery](https://ortexcharts.com/docs/install).

## Re-Exported Types

To avoid importing from four packages, the React package re-exports the types callers
usually need alongside the components:

```ts
import type {
  ChartHandle, ChartOptions, DeepPartial, SeriesModel, SeriesDataInput, SeriesOptionsBase,
  SeriesTypeMap, CrosshairEvent, VisibleRangeEvent,
  FinancialChartHandle, FinancialChartOptions, SeriesMarker, LiveSource, LiveItem, StreamOptions,
  AddIndicatorOptions, IndicatorInstance,
  SparklineHandle, SparklineOptions, SparklineData,
  ChartShellHandle, ChartShellOptions, ChartLayout, OverlayDef,
  Bar,
} from "@ortex-charts/react";
```

## What the Package Does Not Do

- **There is no React renderer for the canvas.** Children of `<Chart>` are effects that call
  the imperative API; they do not reconcile drawing operations.
- **There are no components for drawings, events, order flow, replay or alerts.** Those are
  primitives on the chart, reached through the handle. Wrapping one in a component of your
  own is about ten lines, as in the `SessionShading` example above.
- **`<Series>` does not manage realtime updates.** Use the `SeriesModel` from `ref` and call
  `update`, or hand the chart a `live` source.
