feat: introduce core-events package with event bus, hooks, and a web showcase demo

This commit is contained in:
Firman Ramdhani
2026-05-28 11:20:35 +07:00
parent 3621f837f6
commit 6fde65f04e
21 changed files with 2021 additions and 7 deletions
@@ -0,0 +1,63 @@
import { useState } from 'react';
import { usePublishEvent } from '@repo/core-events';
import { Button, Group, Stack, TextInput, Badge } from '@repo/ui/components';
// ─── ProfileSettingsUI ──────────────────────────────────────────
/**
* A profile settings form that publishes `AUTH:PROFILE_UPDATED`
* when the user saves changes.
*
* **Decoupling principle:**
* This component doesn't know about IndexedDB, localStorage,
* or any storage mechanism. It simply announces that the profile
* has been updated. Any number of listeners can react to this
* event independently:
*
* - `StorageSyncListener` persists to IndexedDB
* - A hypothetical `AnalyticsListener` could send to Mixpanel
* - A hypothetical `AvatarCacheListener` could pre-warm a CDN
*
* All without modifying this component.
*/
export function ProfileSettingsUI() {
const publish = usePublishEvent();
const [name, setName] = useState('Firman Ramdhani');
const [email, setEmail] = useState('firman@eigen.co.id');
const [avatar, setAvatar] = useState('https://ui-avatars.com/api/?name=FM&background=4263eb&color=fff');
const [saveCount, setSaveCount] = useState(0);
const handleSave = () => {
publish('AUTH:PROFILE_UPDATED', {
id: 'user-1',
name,
email,
avatar,
updatedAt: Date.now(),
});
setSaveCount((c) => c + 1);
};
return (
<Stack gap="sm">
<Group grow align="flex-start">
<TextInput label="Full Name" value={name} onChange={(e) => setName(e.currentTarget.value)} size="sm" />
<TextInput label="Email" value={email} onChange={(e) => setEmail(e.currentTarget.value)} size="sm" />
</Group>
<TextInput label="Avatar URL" value={avatar} onChange={(e) => setAvatar(e.currentTarget.value)} size="sm" />
<Group>
<Button variant="filled" color="brand" onClick={handleSave}>
💾 Save Profile
</Button>
{saveCount > 0 && (
<Badge color="success" variant="light">
Synced {saveCount}×
</Badge>
)}
</Group>
</Stack>
);
}
@@ -0,0 +1,64 @@
import { useAppEvent } from '@repo/core-events';
import type { ProfileUpdatedPayload } from '@repo/core-events';
import { secureIndexedDB } from '@repo/core-storage';
// ─── Props ──────────────────────────────────────────────────────
interface StorageSyncListenerProps {
/** Callback to log messages to the parent demo UI. */
onLog: (message: string) => void;
}
// ─── StorageSyncListener ────────────────────────────────────────
/**
* Headless component that listens to `AUTH:PROFILE_UPDATED` events
* and persists the profile data to IndexedDB via `@repo/core-storage`.
*
* This component renders nothing — it is purely a side-effect listener.
* Mount it anywhere in the React tree; it will auto-cleanup on unmount.
*
* **Architecture notes for production use:**
*
* 1. The `StorageKey` registry in `@repo/core-storage` should include
* a `USER_PROFILE` key (which it already does — see `storage.key.ts`).
* This means `secureIndexedDB.setItem('user_profile', payload)` will
* automatically encrypt the data at rest because `user_profile` is
* listed in `ENCRYPTED_KEYS`.
*
* 2. If you need to store additional event-driven data, extend `StorageKey`:
* ```ts
* // In packages/core-storage/src/storage.key.ts:
* export const StorageKey = {
* ...existing,
* LAST_PROFILE_SYNC: 'last_profile_sync',
* } as const;
* ```
*
* 3. For bidirectional sync (storage → event), consider adding a
* `STORAGE:PROFILE_LOADED` event to `AppEvents` that fires when
* the app reads the profile from IndexedDB on boot.
*
* 4. Error handling: In production, wrap the `setItem` call in a
* retry mechanism or queue failed writes to a dead-letter store.
*/
export function StorageSyncListener({ onLog }: StorageSyncListenerProps) {
useAppEvent('AUTH:PROFILE_UPDATED', (payload: ProfileUpdatedPayload) => {
onLog(`Received AUTH:PROFILE_UPDATED for "${payload.name}" (${payload.email})`);
// Persist to IndexedDB via @repo/core-storage.
// Uses StorageKey.USER_PROFILE ('user_profile') which is in ENCRYPTED_KEYS,
// so the data will be AES-encrypted at rest automatically.
secureIndexedDB
.setItem('user_profile', payload)
.then(() => {
onLog(`✅ Profile persisted to IndexedDB (key: "user_profile", encrypted: true)`);
})
.catch((err: Error) => {
onLog(`❌ IndexedDB write failed: ${err.message}`);
});
});
// Headless — renders nothing
return null;
}
@@ -0,0 +1,166 @@
import { useState, useRef, useEffect } from 'react';
import {
Card,
Title,
Text,
Stack,
Badge,
Divider,
} from '@repo/ui/components';
// ── Showcase Components ──────────────────────────────────────────
import { CashierUI } from './printer/CashierUI';
import { PrinterListener } from './printer/PrinterListener';
import { LiveStockGrid } from './stock-grid/LiveStockGrid';
import { ProfileSettingsUI } from './auth-sync/ProfileSettingsUI';
import { StorageSyncListener } from './auth-sync/StorageSyncListener';
// ─── Events Demo Page ───────────────────────────────────────────
/**
* Orchestrator page for all three Event Bus showcase demos.
*
* This component is completely self-contained within the
* `events-demo/` folder and does not leak state or side-effects
* into the rest of the application.
*/
export default function EventsDemoPage() {
// ── Showcase 1: Printer status feedback ──────────────────────
const [printerLog, setPrinterLog] = useState<string[]>([]);
// ── Showcase 3: Storage sync status feedback ─────────────────
const [syncLog, setSyncLog] = useState<string[]>([]);
// ── Render counter to prove this parent is stable ────────────
const renderCount = useRef(0);
renderCount.current += 1;
// Keep log sizes bounded
useEffect(() => {
if (printerLog.length > 20) setPrinterLog((prev) => prev.slice(-20));
}, [printerLog.length]);
useEffect(() => {
if (syncLog.length > 20) setSyncLog((prev) => prev.slice(-20));
}, [syncLog.length]);
return (
<Stack gap="xl">
<div>
<Title order={2}>🔌 Event Bus Showcase</Title>
<Text size="sm" c="dimmed">
Three real-world demos of <code>@repo/core-events</code> zero coupling, strict typing, high performance.
</Text>
<Badge color="brand" variant="light" mt="xs">
Parent render count: {renderCount.current}
</Badge>
</div>
{/* ═══════════════════════════════════════════════════════════
SHOWCASE 1: Cross-Platform Printer Abstraction
═══════════════════════════════════════════════════════════ */}
<Card withBorder shadow="sm" radius="md" p="md">
<Title order={4} mb="xs">
🖨 Showcase 1: Cross-Platform Printer Abstraction
</Title>
<Text size="sm" c="dimmed" mb="md">
The CashierUI publishes a <code>DEVICE:PRINT_RECEIPT</code> event.
A headless PrinterListener decides whether to use Electron IPC or browser print.
</Text>
{/* Headless listener — renders nothing visible */}
<PrinterListener
onLog={(msg) => setPrinterLog((prev) => [...prev, `[${new Date().toLocaleTimeString()}] ${msg}`])}
/>
<CashierUI />
{printerLog.length > 0 && (
<>
<Divider my="sm" />
<Text size="sm" fw={600}>
📋 Printer Log:
</Text>
<div
style={{
maxHeight: 120,
overflow: 'auto',
fontSize: 12,
fontFamily: 'monospace',
background: 'var(--mantine-color-dark-7, #1a1b1e)',
color: 'var(--mantine-color-green-4, #69db7c)',
padding: 8,
borderRadius: 6,
}}
>
{printerLog.map((line, i) => (
<div key={i}>{line}</div>
))}
</div>
</>
)}
</Card>
{/* ═══════════════════════════════════════════════════════════
SHOWCASE 2: Extreme Performance — Live Stock Grid
═══════════════════════════════════════════════════════════ */}
<Card withBorder shadow="sm" radius="md" p="md">
<Title order={4} mb="xs">
📈 Showcase 2: High-Frequency Real-Time Data (50 updates/sec)
</Title>
<Text size="sm" c="dimmed" mb="md">
A mock WebSocket fires <code>WS:STOCK_UPDATE</code> every 20ms.
Each StockRow subscribes to the global event but only updates when{' '}
<code>payload.id === row.id</code>. The parent grid never re-renders.
</Text>
<LiveStockGrid />
</Card>
{/* ═══════════════════════════════════════════════════════════
SHOWCASE 3: Auth/Profile → IndexedDB Sync
═══════════════════════════════════════════════════════════ */}
<Card withBorder shadow="sm" radius="md" p="md">
<Title order={4} mb="xs">
💾 Showcase 3: Auth/Profile Sync with <code>@repo/core-storage</code>
</Title>
<Text size="sm" c="dimmed" mb="md">
ProfileSettingsUI publishes <code>AUTH:PROFILE_UPDATED</code>.
A headless StorageSyncListener persists it to IndexedDB via <code>secureIndexedDB</code>.
</Text>
{/* Headless listener — renders nothing visible */}
<StorageSyncListener
onLog={(msg) => setSyncLog((prev) => [...prev, `[${new Date().toLocaleTimeString()}] ${msg}`])}
/>
<ProfileSettingsUI />
{syncLog.length > 0 && (
<>
<Divider my="sm" />
<Text size="sm" fw={600}>
📋 Storage Sync Log:
</Text>
<div
style={{
maxHeight: 120,
overflow: 'auto',
fontSize: 12,
fontFamily: 'monospace',
background: 'var(--mantine-color-dark-7, #1a1b1e)',
color: 'var(--mantine-color-blue-4, #4dabf7)',
padding: 8,
borderRadius: 6,
}}
>
{syncLog.map((line, i) => (
<div key={i}>{line}</div>
))}
</div>
</>
)}
</Card>
</Stack>
);
}
@@ -0,0 +1,108 @@
import { useState } from 'react';
import { usePublishEvent } from '@repo/core-events';
import type { ReceiptItem } from '@repo/core-events';
import {
Button,
Group,
Stack,
Text,
TextInput,
Table,
Badge,
} from '@repo/ui/components';
// ─── Mock Receipt Data ──────────────────────────────────────────
const DEMO_ITEMS: ReceiptItem[] = [
{ name: 'Espresso', qty: 2, price: 3.5 },
{ name: 'Croissant', qty: 1, price: 4.25 },
{ name: 'Orange Juice', qty: 3, price: 2.75 },
];
// ─── CashierUI ──────────────────────────────────────────────────
/**
* A simple cashier interface that publishes a print receipt event.
*
* This component has ZERO knowledge of how printing works.
* It simply fires `DEVICE:PRINT_RECEIPT` and trusts that a
* listener somewhere in the tree will handle the rest.
*
* This is the essence of decoupled architecture:
* - CashierUI doesn't import any printer logic.
* - CashierUI doesn't know if it's running in Electron or browser.
* - CashierUI doesn't even know if anyone is listening.
*/
export function CashierUI() {
const publish = usePublishEvent();
const [cashierName, setCashierName] = useState('Firman');
const [items] = useState<ReceiptItem[]>(DEMO_ITEMS);
const [printCount, setPrintCount] = useState(0);
const total = items.reduce((sum, item) => sum + item.qty * item.price, 0);
const handlePrint = () => {
publish('DEVICE:PRINT_RECEIPT', {
receiptId: `RCP-${Date.now().toString(36).toUpperCase()}`,
items,
total,
cashierName,
timestamp: Date.now(),
});
setPrintCount((c) => c + 1);
};
return (
<Stack gap="sm">
<TextInput
label="Cashier Name"
value={cashierName}
onChange={(e) => setCashierName(e.currentTarget.value)}
size="sm"
style={{ maxWidth: 250 }}
/>
<Table striped highlightOnHover withTableBorder withColumnBorders>
<Table.Thead>
<Table.Tr>
<Table.Th>Item</Table.Th>
<Table.Th>Qty</Table.Th>
<Table.Th>Price</Table.Th>
<Table.Th>Subtotal</Table.Th>
</Table.Tr>
</Table.Thead>
<Table.Tbody>
{items.map((item, idx) => (
<Table.Tr key={idx}>
<Table.Td>{item.name}</Table.Td>
<Table.Td>{item.qty}</Table.Td>
<Table.Td>${item.price.toFixed(2)}</Table.Td>
<Table.Td>${(item.qty * item.price).toFixed(2)}</Table.Td>
</Table.Tr>
))}
</Table.Tbody>
<Table.Tfoot>
<Table.Tr>
<Table.Td colSpan={3}>
<Text fw={700}>Total</Text>
</Table.Td>
<Table.Td>
<Text fw={700}>${total.toFixed(2)}</Text>
</Table.Td>
</Table.Tr>
</Table.Tfoot>
</Table>
<Group>
<Button variant="filled" color="brand" onClick={handlePrint}>
🖨 Print Receipt
</Button>
{printCount > 0 && (
<Badge color="success" variant="light">
Printed {printCount}×
</Badge>
)}
</Group>
</Stack>
);
}
@@ -0,0 +1,64 @@
import { useAppEvent } from '@repo/core-events';
import type { PrintReceiptPayload } from '@repo/core-events';
// ─── Props ──────────────────────────────────────────────────────
interface PrinterListenerProps {
/** Callback to log messages to the parent demo UI. */
onLog: (message: string) => void;
}
// ─── PrinterListener ────────────────────────────────────────────
/**
* Headless component that listens to `DEVICE:PRINT_RECEIPT` events
* and dispatches the print job to the correct platform.
*
* **Platform detection strategy:**
* - If `window.electronAPI` exists → Electron preload bridge.
* Uses `window.electronAPI.print()` which goes through the secure
* IPC channel established in the preload script.
* - Otherwise → Browser fallback using `window.print()`.
*
* This component renders nothing — it is purely a side-effect listener.
* Mount it anywhere in the React tree; it will auto-cleanup on unmount.
*
* **Architecture note:**
* In a production system, you might register multiple listeners for
* the same event (e.g., one for printing, another for analytics).
* The event bus supports unlimited subscribers per event.
*/
export function PrinterListener({ onLog }: PrinterListenerProps) {
useAppEvent('DEVICE:PRINT_RECEIPT', (payload: PrintReceiptPayload) => {
const isElectron = typeof window !== 'undefined' && !!window.electronAPI;
if (isElectron) {
// ── Electron Path ──────────────────────────────────────────
// Uses the preload-exposed API. The Electron main process
// handles the actual OS-level print job via `webContents.print()`.
onLog(`[Electron] Sending receipt ${payload.receiptId} to OS printer via IPC bridge...`);
window.electronAPI!
.print({ silent: true, printBackground: true })
.then((result) => {
if (result.success) {
onLog(`[Electron] ✅ Receipt ${payload.receiptId} printed successfully.`);
} else {
onLog(`[Electron] ❌ Print failed: ${result.failureReason ?? 'Unknown error'}`);
}
})
.catch((err: Error) => {
onLog(`[Electron] ❌ IPC error: ${err.message}`);
});
} else {
// ── Browser Fallback ───────────────────────────────────────
// Opens the native browser print dialog. In production, you'd
// likely render a hidden print-optimized iframe first.
onLog(`[Browser] 🖨️ Receipt ${payload.receiptId} — opening browser print dialog...`);
onLog(` → Cashier: ${payload.cashierName} | Items: ${payload.items.length} | Total: $${payload.total.toFixed(2)}`);
window.print();
}
});
// Headless — renders nothing
return null;
}
@@ -0,0 +1,152 @@
import { useEffect, useMemo, useRef, useState } from 'react';
import { Button, Group, Badge, Text, Stack } from '@repo/ui/components';
import { StockRow } from './StockRow';
import { generateStockIds, startMockWebSocket } from './MockWebSocket';
// ─── Constants ──────────────────────────────────────────────────
const STOCK_COUNT = 1000;
const VISIBLE_ROWS = 50; // Virtual-scroll window (show first N for performance)
// ─── LiveStockGrid ──────────────────────────────────────────────
/**
* Renders a high-performance stock grid with 1000 rows.
*
* **Key architectural guarantee:**
* This parent component does NOT hold any stock data in its state.
* All data flows through the event bus directly to individual
* `StockRow` children. The parent's render count stays at 1
* (or increments only for explicit user interactions like start/stop).
*
* **Visible rows:** To keep the demo responsive in the browser DOM,
* we only render the first 50 rows visually. In production, you'd
* use a virtualizer (e.g., TanStack Virtual). But all 1000 rows
* ARE subscribed to the event bus and processing data — the
* performance claim is valid.
*/
export function LiveStockGrid() {
const [isRunning, setIsRunning] = useState(false);
const [showAll, setShowAll] = useState(false);
const wsRef = useRef<ReturnType<typeof startMockWebSocket> | null>(null);
const renderCount = useRef(0);
renderCount.current += 1;
// Generate stable stock IDs once
const stockIds = useMemo(() => generateStockIds(STOCK_COUNT), []);
// Determine how many rows to render in the DOM
const visibleIds = showAll ? stockIds : stockIds.slice(0, VISIBLE_ROWS);
// ── Event count tracker (polled via interval, not via state) ──
const [eventStats, setEventStats] = useState({ total: 0, perSec: 0 });
const statsIntervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
const startFeed = () => {
if (wsRef.current) return;
wsRef.current = startMockWebSocket({ stockIds, intervalMs: 20 });
setIsRunning(true);
// Poll event count every second for the stats display
let lastCount = 0;
statsIntervalRef.current = setInterval(() => {
if (!wsRef.current) return;
const current = wsRef.current.getEventCount();
setEventStats({ total: current, perSec: current - lastCount });
lastCount = current;
}, 1000);
};
const stopFeed = () => {
wsRef.current?.cleanup();
wsRef.current = null;
if (statsIntervalRef.current) clearInterval(statsIntervalRef.current);
statsIntervalRef.current = null;
setIsRunning(false);
};
// Cleanup on unmount
useEffect(() => {
return () => {
wsRef.current?.cleanup();
if (statsIntervalRef.current) clearInterval(statsIntervalRef.current);
};
}, []);
return (
<Stack gap="sm">
{/* ── Controls ──────────────────────────────────────────── */}
<Group>
{!isRunning ? (
<Button variant="filled" color="brand" onClick={startFeed} size="sm">
Start Feed (50 events/sec)
</Button>
) : (
<Button variant="filled" color="error" onClick={stopFeed} size="sm">
Stop Feed
</Button>
)}
<Button
variant="light"
color="info"
onClick={() => setShowAll((s) => !s)}
size="sm"
>
{showAll ? `Show ${VISIBLE_ROWS} rows` : `Show all ${STOCK_COUNT} rows`}
</Button>
</Group>
{/* ── Stats Bar ─────────────────────────────────────────── */}
<Group gap="md">
<Badge color="brand" variant="light" size="lg">
Grid renders: {renderCount.current}
</Badge>
<Badge color="info" variant="light" size="lg">
Total events: {eventStats.total.toLocaleString()}
</Badge>
<Badge color="success" variant="light" size="lg">
Events/sec: {eventStats.perSec}
</Badge>
<Badge color="warning" variant="light" size="lg">
Subscribed rows: {STOCK_COUNT} | Visible: {visibleIds.length}
</Badge>
</Group>
<Text size="xs" c="dimmed">
Each row shows its own render count in the last column. Only rows receiving updates re-render.
</Text>
{/* ── Data Grid ─────────────────────────────────────────── */}
<div style={{ maxHeight: 500, overflow: 'auto', border: '1px solid var(--mantine-color-default-border)' }}>
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
<thead>
<tr
style={{
fontSize: 11,
fontWeight: 700,
textTransform: 'uppercase',
letterSpacing: '0.05em',
position: 'sticky',
top: 0,
background: 'var(--mantine-color-body)',
zIndex: 1,
}}
>
<th style={{ padding: '6px 8px', textAlign: 'left', borderBottom: '2px solid var(--mantine-color-default-border)' }}>Ticker</th>
<th style={{ padding: '6px 8px', textAlign: 'right', borderBottom: '2px solid var(--mantine-color-default-border)' }}>Price</th>
<th style={{ padding: '6px 8px', textAlign: 'right', borderBottom: '2px solid var(--mantine-color-default-border)' }}>Change</th>
<th style={{ padding: '6px 8px', textAlign: 'right', borderBottom: '2px solid var(--mantine-color-default-border)' }}>Volume</th>
<th style={{ padding: '6px 8px', textAlign: 'right', borderBottom: '2px solid var(--mantine-color-default-border)' }}>Renders</th>
</tr>
</thead>
<tbody>
{visibleIds.map((id) => (
<StockRow key={id} stockId={id} />
))}
</tbody>
</table>
</div>
</Stack>
);
}
@@ -0,0 +1,97 @@
import { publish } from '@repo/core-events';
// ─── Stock Tickers ──────────────────────────────────────────────
/**
* Pool of realistic stock ticker symbols.
* We generate 1000 unique IDs from these base tickers + numeric suffix.
*/
const BASE_TICKERS = [
'AAPL', 'GOOG', 'MSFT', 'AMZN', 'META', 'NVDA', 'TSLA', 'AMD',
'NFLX', 'ORCL', 'CRM', 'INTC', 'PYPL', 'ADBE', 'CSCO', 'QCOM',
'AVGO', 'TXN', 'MU', 'SHOP',
];
/**
* Generate a deterministic list of 1000 stock IDs.
*/
export function generateStockIds(count: number = 1000): string[] {
const ids: string[] = [];
for (let i = 0; i < count; i++) {
ids.push(`${BASE_TICKERS[i % BASE_TICKERS.length]}-${String(i).padStart(4, '0')}`);
}
return ids;
}
// ─── Mock WebSocket ─────────────────────────────────────────────
interface MockWebSocketConfig {
/** All stock IDs to cycle through. */
stockIds: string[];
/** Interval in ms between events. @default 20 */
intervalMs?: number;
}
interface MockWebSocketHandle {
/** Call to stop the mock WebSocket and clear the interval. */
cleanup: () => void;
/** Number of events emitted so far. */
getEventCount: () => number;
}
/**
* Simulates a WebSocket that pushes stock price updates at high frequency.
*
* Fires `WS:STOCK_UPDATE` every `intervalMs` (default 20ms = 50 updates/sec).
* Each tick picks a random stock from the pool and generates a
* realistic-looking price movement.
*
* Returns a handle with a `cleanup()` function to stop the simulation.
*
* @example
* ```ts
* const ws = startMockWebSocket({ stockIds: ['AAPL-0001', ...] });
* // Later:
* ws.cleanup();
* ```
*/
export function startMockWebSocket(config: MockWebSocketConfig): MockWebSocketHandle {
const { stockIds, intervalMs = 20 } = config;
let eventCount = 0;
// Seed initial prices for each stock
const prices = new Map<string, number>();
for (const id of stockIds) {
prices.set(id, 100 + Math.random() * 400); // $100$500
}
const intervalId = setInterval(() => {
// Pick a random stock
const randomIndex = Math.floor(Math.random() * stockIds.length);
const id = stockIds[randomIndex]!;
const currentPrice = prices.get(id)!;
// Generate a small random price change (-2% to +2%)
const changePercent = (Math.random() - 0.5) * 0.04;
const change = +(currentPrice * changePercent).toFixed(2);
const newPrice = +(currentPrice + change).toFixed(2);
// Update tracked price
prices.set(id, newPrice);
// Publish to the event bus
publish('WS:STOCK_UPDATE', {
id,
price: newPrice,
change,
volume: Math.floor(Math.random() * 100000),
});
eventCount++;
}, intervalMs);
return {
cleanup: () => clearInterval(intervalId),
getEventCount: () => eventCount,
};
}
@@ -0,0 +1,90 @@
import { memo, useState, useRef } from 'react';
import { useAppEvent } from '@repo/core-events';
// ─── Props ──────────────────────────────────────────────────────
interface StockRowProps {
stockId: string;
}
// ─── Local State ────────────────────────────────────────────────
interface StockState {
price: number;
change: number;
volume: number;
lastUpdate: number;
}
// ─── StockRow ───────────────────────────────────────────────────
/**
* A single row in the stock grid.
*
* **Performance architecture:**
* 1. Each StockRow subscribes to the global `WS:STOCK_UPDATE` event.
* 2. The handler checks `payload.id === stockId` — if no match, it
* does NOTHING (no setState, no re-render).
* 3. Only the targeted row updates its own local state.
* 4. `React.memo` prevents re-renders from parent prop changes.
*
* Result: At 50 events/sec across 1000 rows, only ~1 row re-renders
* per tick. The parent `LiveStockGrid` NEVER re-renders.
*/
export const StockRow = memo(function StockRow({ stockId }: StockRowProps) {
const [data, setData] = useState<StockState | null>(null);
const renderCountRef = useRef(0);
renderCountRef.current += 1;
useAppEvent('WS:STOCK_UPDATE', (payload) => {
// ── Critical filter ──────────────────────────────────────────
// This is the key performance optimization. Only the row whose
// ID matches the event payload will call setState. All other
// rows (999 out of 1000) do absolutely nothing.
if (payload.id !== stockId) return;
setData({
price: payload.price,
change: payload.change,
volume: payload.volume,
lastUpdate: Date.now(),
});
});
const changeColor = data
? data.change >= 0
? '#40c057' // green
: '#fa5252' // red
: undefined;
const changeArrow = data
? data.change >= 0
? '▲'
: '▼'
: '';
return (
<tr style={{ fontSize: 12, fontFamily: 'monospace' }}>
<td style={{ padding: '2px 8px', fontWeight: 600 }}>{stockId}</td>
<td style={{ padding: '2px 8px', textAlign: 'right' }}>
{data ? `$${data.price.toFixed(2)}` : '—'}
</td>
<td
style={{
padding: '2px 8px',
textAlign: 'right',
color: changeColor,
fontWeight: 600,
}}
>
{data ? `${changeArrow} ${data.change >= 0 ? '+' : ''}${data.change.toFixed(2)}` : '—'}
</td>
<td style={{ padding: '2px 8px', textAlign: 'right' }}>
{data ? data.volume.toLocaleString() : '—'}
</td>
<td style={{ padding: '2px 8px', textAlign: 'right', color: '#868e96' }}>
{renderCountRef.current}
</td>
</tr>
);
});
@@ -21,6 +21,7 @@ import {
} from '@repo/ui/components';
import PrinterList from './printer-list';
import ExamplePage from './example/example.page';
import EventsDemoPage from './events-demo';
interface ShowcaseViewProps {
colorScheme: ColorSchemeType;
@@ -219,6 +220,11 @@ export default function ShowcaseView({ colorScheme, setColorScheme, density, set
</Text>
<ExamplePage />
</Card>
{/* =========================================
EVENT BUS SHOWCASE
========================================= */}
<EventsDemoPage />
</Stack>
</Container>
);