Engineering NotebookTrading

Building a Markets Dashboard: Charts, State, and Real-Time Data Done Right

A technical guide to building a clean, fast markets dashboard with React, Recharts, and live data — framed as a systems exercise, for learning purposes only.

JM
Jordan Morris
Founder & CEO · May 19, 2026 · 10 min read
Sharer/Y𝕏inf

A markets dashboard looks simple from the outside: a few charts, some numbers ticking, maybe a watchlist. Build one and you learn that the chart is the easy part. The hard parts are everything around it — how data arrives, how state stays coherent while a feed pushes dozens of updates a second, and how you render all of that without the browser melting. This is a walk through building that dashboard with React, Recharts, and live data, treated as what it actually is: a systems exercise. The build teaches you more about how markets are wired than any tip ever will. To be clear up front, this is educational and tooling-focused — nothing here is a recommendation to trade.

We build data-heavy interfaces constantly across the studio — dashboards inside Supreme Suite, live admin panels, logistics tracking. A markets dashboard is the same problem class turned up to eleven: the update frequency is brutal, the data is numeric and time-ordered, and users notice every dropped frame. So it makes a good teaching build. We will go in the order the data flows: ingest, state, render, then performance.

Data first: fetching, then streaming

Every markets dashboard has two data modes, and conflating them is the first mistake people make. There is the snapshot — the historical series you load once to draw a chart — and the stream — the live updates that arrive afterward. You fetch the snapshot over plain HTTP. You subscribe to the stream over a WebSocket. They are different transports for different jobs, and the dashboard's whole job is to stitch them into one coherent view.

The pattern is: fetch history, paint the chart, then open the socket and start appending. If you open the socket first you get updates with no chart to put them on. If you fetch history but never reconcile the gap between your snapshot's last timestamp and the socket's first message, you get a visible hole or a duplicated candle. Reconciliation at the seam is where correctness lives.

typescript
// Two transports, one series. Fetch the snapshot, then stream the tail.
async function loadSeries(symbol: string): Promise<Candle[]> {
  const res = await fetch(`/api/history?symbol=${symbol}&tf=1m`);
  if (!res.ok) throw new Error(`history ${res.status}`);
  return res.json(); // [{ t, o, h, l, c, v }, ...] ascending by time
}

function openStream(symbol: string, onTick: (t: Tick) => void) {
  const ws = new WebSocket(`wss://feed.example/quotes?symbol=${symbol}`);
  ws.onmessage = (e) => onTick(JSON.parse(e.data));
  // Always handle the unhappy paths — feeds drop constantly.
  ws.onclose = () => scheduleReconnect(symbol, onTick);
  ws.onerror = () => ws.close();
  return ws;
}

If you want to go deeper on where these feeds come from and how order books and ticks actually behave, that is its own topic — we cover it in Market Structure for Builders. For the dashboard, the relevant fact is just that the stream is unreliable and unordered enough that you must defend against it.

State: one source of truth, not ten

The instinct is to drop incoming ticks straight into component state and let React sort it out. Do that and you will have a watchlist component, three charts, and a header ribbon all subscribing to the same socket, each holding its own slightly different copy of the truth. They drift. One updates, another lags a frame, and the dashboard contradicts itself on screen.

The fix is architectural: the socket writes into one store, and every component reads from that store. The feed has a single owner. Components are subscribers, not subscribers-and-mutators. This is the same discipline as tenant isolation or any other single-source-of-truth rule — the data has exactly one place it lives, and everything else is a view onto it.

typescript
// One store owns the feed. Components read; they never subscribe directly.
import { create } from "zustand";

type Quote = { price: number; ts: number };
type Store = {
  quotes: Record<string, Quote>;          // symbol -> latest
  series: Record<string, Candle[]>;        // symbol -> history
  applyTick: (t: Tick) => void;
};

export const useMarket = create<Store>((set) => ({
  quotes: {},
  series: {},
  applyTick: (t) =>
    set((s) => ({
      quotes: { ...s.quotes, [t.symbol]: { price: t.price, ts: t.ts } },
    })),
}));

// A component selects only the slice it needs — so a EURUSD tick
// never re-renders the panel that is watching gold.
function PriceCell({ symbol }: { symbol: string }) {
  const price = useMarket((s) => s.quotes[symbol]?.price);
  return <span className="tabular-nums">{price ?? "—"}</span>;
}

Note what the store deliberately does not do: it does not store derived numbers. Percentage change, moving averages, the color of an up-tick — those are computed at read time, memoized per component. Storing derived state is how you get bugs where the number and its color disagree. Store the raw truth; derive the rest.

Rendering with Recharts

Recharts is a good default for a markets dashboard because it is declarative, composable, and renders SVG — which is crisp, themeable, and trivially styled to a strict black-and-white house look. You describe the chart as components — an axis here, a line there, a tooltip — and Recharts draws it. For line charts, area charts, sparklines in a watchlist, and most analytic overlays, it is more than enough.

tsx
// A price line that stays readable under a strict mono theme.
import { LineChart, Line, XAxis, YAxis, Tooltip, ResponsiveContainer } from "recharts";

function PriceChart({ data }: { data: Candle[] }) {
  return (
    <ResponsiveContainer width="100%" height={320}>
      <LineChart data={data} margin={{ top: 8, right: 16, bottom: 0, left: 0 }}>
        <XAxis dataKey="t" tickFormatter={fmtTime} minTickGap={48} />
        <YAxis domain={["dataMin", "dataMax"]} width={56}
               tickFormatter={(v: number) => v.toFixed(2)} />
        <Tooltip formatter={(v: any) => [Number(v).toFixed(2), "price"]} />
        <Line type="monotone" dataKey="c" stroke="#0a0a0a"
              strokeWidth={1.5} dot={false} isAnimationActive={false} />
      </LineChart>
    </ResponsiveContainer>
  );
}

Two details in that snippet matter more than they look. isAnimationActive={false} is not cosmetic — chart enter/update animations are pure overhead on a series that changes every second, and they make the line lie by smoothing between states. And dot={false} removes thousands of per-point SVG nodes you will never see at this density. On a live chart, animation and dots are both things you turn off, not on.

Fig 1 — the data path: an HTTP snapshot and a WebSocket stream converge into one store; Recharts and the watchlist are read-only views downstream. The store is the only writer.

Performance under frequent updates

Here is where a markets dashboard separates from a normal CRUD UI. A feed can push faster than a screen can refresh — 60Hz is your ceiling, and the feed does not care. If you call setState on every tick, you queue more renders than the browser can flush, the main thread backs up, and the whole dashboard goes choppy precisely when the market is most active, which is exactly when the user needs it responsive. The defense is to decouple ingest rate from render rate.

  1. Batch with the frame, not the tick. Buffer incoming ticks and flush the buffer once per animation frame with requestAnimationFrame. The feed can scream at 200 messages a second; you still only repaint ~60 times. The user cannot perceive faster than that anyway.
  2. Select narrowly. As covered above, a component should subscribe to the smallest slice it can. The gold panel must not re-render because a forex pair ticked. Narrow selectors are the difference between repainting one cell and repainting the page.
  3. Cap the series length. A live chart does not need ten thousand points in memory; it needs the visible window plus a buffer. Trim old data as new data arrives so the array — and the SVG node count — stays bounded.
  4. Memoize the expensive parts. Wrap chart components in React.memo, derive indicators with useMemo, and keep callbacks stable so children do not re-render for free.
  5. Throttle the cosmetic stuff. A flashing up/down color or a pulsing dot does not need to fire on every tick. Throttling visual flourishes to a few times a second looks identical to the eye and costs a fraction of the work.
typescript
// Decouple ingest from render: buffer ticks, flush once per frame.
let buffer: Tick[] = [];
let scheduled = false;

function onTick(t: Tick) {
  buffer.push(t);
  if (scheduled) return;
  scheduled = true;
  requestAnimationFrame(() => {
    const batch = buffer;
    buffer = [];
    scheduled = false;
    // One state update for the whole frame's worth of ticks.
    useMarket.getState().applyBatch(batch);
  });
}

That single pattern — buffer, flush on frame — is the highest-leverage thing in the entire build. It converts an unbounded, bursty input into a steady, capped render load, and it is the reason a dashboard stays smooth during a volatility spike instead of stuttering exactly when it matters.

1
store owns the feed
60fps
render ceiling to design for
2
transports: snapshot + stream

What the build actually teaches

By the time you have a markets dashboard that does not stutter, you have absorbed a set of facts about markets that no summary could have taught you. You learn that price is not one number but a stream of disagreeing quotes. You learn that data arrives late, out of order, and sometimes not at all. You learn that 'the price' is a question of which feed, at which instant, with what latency — and that every clean chart you have ever seen is hiding a pile of reconciliation logic underneath.

That is the real argument for building the tool yourself rather than chasing signals. The build forces you to confront market structure — feeds, latency, ordering, staleness — as concrete engineering problems. It replaces a vague sense that markets are noisy with a precise understanding of how they are noisy. The same instinct shows up in the psychology of trading through a systems lens: rules and process over reflexes and tips. A dashboard is a process made visible. It will not tell you what to do — and it should not — but it will teach you, honestly, what you are looking at.

Build the instrument before you trust the reading. The wiring teaches you more about the market than the number ever will.

Studio principle

Need a real-time dashboard built right — live data, clean state, charts that stay smooth under load? That is exactly the kind of system we ship.

Start a project
#react#recharts#real-time-data#dashboards#performance

Questions, answered

Should I use Recharts or a canvas charting library for a markets dashboard?
Use Recharts for line charts, area charts, sparklines in a watchlist, and analytic overlays — it is declarative, renders crisp SVG, and themes cleanly. Reach for a canvas-based financial charting library when your dashboard is fundamentally candlestick-and-volume with crosshairs and tens of thousands of bars, because Recharts has no first-class OHLC series and SVG node counts get expensive at that density. Match the tool to the chart, not the other way around.
How do I keep a React dashboard fast when a feed pushes many updates per second?
Decouple ingest rate from render rate. Buffer incoming ticks and flush the buffer once per animation frame with requestAnimationFrame, so a 200-message-per-second feed still only repaints around 60 times a second. Pair that with narrow store selectors so each component re-renders only when its own slice changes, cap the in-memory series length to the visible window, and turn off chart animations and per-point dots on live series.
Is this dashboard meant to give trading signals or advice?
No. This is an educational, tooling-focused build. The dashboard displays market data as a software artifact — it does not produce buy or sell calls, price targets, strategies, or return guarantees, and nothing in this article should be read as financial advice. The value is in what building it teaches you about market structure, data, and process, not in any signal it might appear to show.
Recommended · affiliate

The stack we actually use

The tools below run our own systems and the ones we build for clients. A few are affiliate links — they support The Signal at no cost to you.

Some links on this site are affiliate links. If you click through and sign up or buy, J Supreme Tech may earn a commission at no extra cost to you. We only recommend tools we actually use to build and run our own systems. Full disclosure.

Found the signal useful? Pass it on.

Sharer/Y𝕏inf
The Communications Debrief

Get the signal in your inbox.

Field notes on building software, marketing systems and the markets — what we shipped, the problems and the fixes. One clean dispatch, no noise.

Double opt-in. No spam, unsubscribe anytime.