Realtime dashboard
▶ Run live · View source on GitHub
A simulated live ticker dashboard. 500 rows, ~60 of them update per “WebSocket message” at 30 Hz. There’s a tiny imperatively-drawn canvas in the header to prove the morph leaves library-owned subtrees alone.
What to look at:
each()at scale. The ticker table runs through the keyed list reconciler. Per-row memoisation by identity plus an optional memo key (\${id}-${price}“) means a row whose price didn’t change skips JSX evaluation, string-building, and the morph walk. Only the ~60 rows that actually moved get touched.batch()wraps the simulated WS message handler. Inside,tickers.value,frame.value, andconnected.valueall change —batch()ensures the surroundingeffect()s and the mount’s render fn each run once at the end, not three times.data-morph-skipon the chart canvas. The canvas’srequestAnimationFrameloop draws independently; without skip, every dashboard tick (~30/s) would walk into the canvas’s children and clobber its DOM.effect()for WS lifecycle. The simulated feed starts insideeffect()and returns a cleanup that clears the interval and flipsconnectedto false. In a real app you’d open the WebSocket here and close it in the cleanup.computed()for derived stats.upPctis recomputed only when the ticker array reference changes (i.e. once perbatch()).
// site/src/examples/complete/dashboard/main.tsx (excerpt — full source on GitHub)import { signal, computed, batch, effect, mount, each } from 'kerfjs';
const tickers = signal<Ticker[]>(genSymbols()); // 500 rowsconst connected = signal(false);const frame = signal(0);const upPct = computed(() => /* % of tickers up vs prev */);
mount(root, () => ( <div class="dash"> <header> <span class={`status ${connected.value ? 'live' : 'down'}`}> ● {connected.value ? 'LIVE' : 'DISCONNECTED'} </span> <span>tick #{frame.value}</span> <span>{upPct.value}% up</span> <div class="chart-host" data-morph-skip> <canvas id="chart" width="240" height="40"></canvas> </div> </header> <table class="tickers"> <tbody> {each( tickers.value, (t) => /* row JSX */, (t) => `${t.id}-${t.price.toFixed(2)}`, // memo key — unchanged price → reused HTML )} </tbody> </table> </div>));
// Imperative canvas, set up once. data-morph-skip keeps the diff away.const ctx = (document.getElementById('chart') as HTMLCanvasElement).getContext('2d')!;function draw() { /* rAF loop */ ; requestAnimationFrame(draw); }draw();
// effect() = "WS lifecycle". Starts the simulated feed; cleanup stops it.effect(() => { connected.value = true; const interval = setInterval(() => { batch(() => { // …mutate ~60 of 500 ticker rows + bump frame counter }); }, 33); return () => { clearInterval(interval); connected.value = false; };});