State, save & load
The serializable TradingChartState: controlled mode, save and load.
"use client";
import { TradingChart, type TradingChartHandle, type TradingChartState } from "@reincharts/widget";
import { useRef, useState } from "react";
import { ToolbarButton, WithToolbar } from "@/components/preview/controls";
import { createDemoFeed } from "@/lib/demo-feed";
import { useDocsChartTheme } from "@/lib/use-docs-theme";
const KEY = "reincharts-docs-layout";
export default function SaveAndLoadLayout() {
const theme = useDocsChartTheme();
const [feed] = useState(() => createDemoFeed());
const chart = useRef<TradingChartHandle>(null);
const [info, setInfo] = useState("Add indicators or drawings, then save");
return (
<WithToolbar
toolbar={
<>
<ToolbarButton
onClick={() => {
// the whole layout: symbol, interval, chart type, indicators, drawings, panes, range...
const state = chart.current!.save();
localStorage.setItem(KEY, JSON.stringify(state));
setInfo(
`Saved ${state.indicators.length} indicators and ${state.drawings.length} drawings`,
);
}}
>
Save layout
</ToolbarButton>
<ToolbarButton
onClick={() => {
const json = localStorage.getItem(KEY);
if (json) {
chart.current!.load(json);
setInfo("Loaded the saved layout");
}
}}
>
Load layout
</ToolbarButton>
<ToolbarButton onClick={() => chart.current!.addIndicator("macd")}>Add MACD</ToolbarButton>
<span className="text-xs text-fd-muted-foreground">{info}</span>
</>
}
>
<TradingChart
ref={chart}
datafeed={feed}
defaultSymbol="ACME"
defaultResolution="60"
defaultIndicators={["sma"]}
onStateChange={(state: TradingChartState) =>
setInfo(`${state.indicators.length} indicators · ${state.drawings.length} drawings (unsaved)`)
}
theme={theme}
/>
</WithToolbar>
);
}Everything the user can change is one plain object, TradingChartState: symbol, interval, chart type, scale mode,
indicators, drawings, compare symbols, pane sizes, visible range, theme, layout and its symbols, favorite intervals and
settings. Store it per user, per symbol, in a URL...
Save and load with the ref
const chart = useRef<TradingChartHandle>(null);
<TradingChart ref={chart} datafeed={feed} />;
const state = chart.current!.save(); // includes the current pane sizes and visible range
await api.saveLayout(JSON.stringify(state));
chart.current!.load(await api.loadLayout()); // an object or its JSON stringUncontrolled with a saved layout
<TradingChart
datafeed={feed}
defaultState={saved} // e.g. the result of save()
onStateChange={(state) => localStorage.setItem("layout", serializeState(state))}
/>Controlled
Own the state completely (Redux, Zustand, a server cache) with state + onStateChange:
const [state, setState] = useState(() => parseState(localStorage.getItem("layout")));
<TradingChart datafeed={feed} state={state} onStateChange={setState} />;serializeState, parseState (tolerates invalid input and fills defaults), normalizeState and defaultState help
with storage and migrations.
The layout JSON
{
"symbol": "AAPL",
"resolution": "60",
"chartType": "candles",
"scaleMode": "linear",
"indicators": [
{ "id": "ema-1", "definitionId": "ema", "inputs": { "period": 21 }, "paneId": "price" },
{ "id": "rsi-1", "definitionId": "rsi", "paneId": "pane-rsi-1" }
],
"drawings": [
{
"id": "d1",
"type": "trendLine",
"pane": "price",
"points": [
{ "time": 1717000000000, "price": 182.4 },
{ "time": 1717600000000, "price": 191.2 }
],
"style": { "color": "#2962ff" }
}
],
"paneSizes": { "price": 3, "pane-rsi-1": 1 },
"visibleRange": { "from": 1716000000000, "to": 1718000000000 },
"layout": "single",
"theme": "dark",
"favorites": ["5", "15", "60", "1D"]
}Drawings use the same format as @reincharts/interactive, and indicators reference the
indicator registry (or your extraIndicators) by id, so saved layouts stay small and readable.