Real-time Data
A live stock ticker that receives price updates over a WebSocket connection and renders them reactively. Demonstrates signals with external data sources and lifecycle cleanup.
What You’ll Build
Section titled “What You’ll Build”- Connect to a WebSocket for live price updates
- Display a table of stock prices that update in real time
- Show connection status (connected/disconnected)
- Color-code price changes (green for up, red for down)
- Clean up the WebSocket connection when the component unmounts
Data Store
Section titled “Data Store”import { signal, computed } from "@whisq/core";
export interface Stock { symbol: string; price: number; change: number;}
export const stocks = signal<Map<string, Stock>>(new Map());export const connected = signal(false);
export const stockList = computed(() => Array.from(stocks.value.values()) .sort((a, b) => a.symbol.localeCompare(b.symbol)));
export const updateStock = (symbol: string, price: number) => { const current = stocks.value.get(symbol); const change = current ? price - current.price : 0; const updated = new Map(stocks.value); updated.set(symbol, { symbol, price, change }); stocks.value = updated;};WebSocket Hook
Section titled “WebSocket Hook”import { onMount, onCleanup } from "@whisq/core";import { connected, updateStock } from "../stores/ticker";
export function useTickerSocket(url: string) { onMount(() => { const ws = new WebSocket(url);
ws.onopen = () => { connected.value = true; };
ws.onmessage = (event) => { const data = JSON.parse(event.data); updateStock(data.symbol, data.price); };
ws.onclose = () => { connected.value = false; };
onCleanup(() => { ws.close(); }); });}Ticker Component
Section titled “Ticker Component”import { component, div, h1, table, thead, tbody, tr, th, td, span, each } from "@whisq/core";import { stockList, connected } from "../stores/ticker";import { useTickerSocket } from "../hooks/useWebSocket";
const Ticker = component(() => { useTickerSocket("wss://example.com/stocks");
return div({ class: "ticker" }, div({ class: "header" }, h1("Live Prices"), span({ class: () => connected.value ? "status connected" : "status disconnected", }, () => connected.value ? "Connected" : "Disconnected"), ),
table( thead( tr( th("Symbol"), th("Price"), th("Change"), ), ), tbody( each(() => stockList.value, (stock) => tr( td({ class: "symbol" }, stock.symbol), td(() => `$${stock.price.toFixed(2)}`), td({ class: () => stock.change > 0 ? "up" : stock.change < 0 ? "down" : "", }, () => { const sign = stock.change > 0 ? "+" : ""; return `${sign}${stock.change.toFixed(2)}`; }), ) ), ), ), );});
export default Ticker;Entry Point
Section titled “Entry Point”import { mount } from "@whisq/core";import Ticker from "./components/Ticker";
mount(Ticker({}), document.getElementById("app")!);How It Works
Section titled “How It Works”1. External data feeds into signals
ws.onmessage = (event) => { const data = JSON.parse(event.data); updateStock(data.symbol, data.price);};WebSocket messages update a signal. Every component reading that signal updates automatically — no event bus, no pubsub, no manual DOM updates.
2. Immutable Map updates
const updated = new Map(stocks.value);updated.set(symbol, { symbol, price, change });stocks.value = updated;Creating a new Map triggers reactivity. Mutating the existing Map would not.
3. Lifecycle cleanup
onMount(() => { const ws = new WebSocket(url); // ... onCleanup(() => { ws.close(); });});onCleanup runs when the component unmounts, closing the WebSocket connection.
4. Reactive CSS classes
td({ class: () => stock.change > 0 ? "up" : stock.change < 0 ? "down" : "",})The class changes reactively based on the price change direction.
Key Concepts
Section titled “Key Concepts”- Signals with external data — WebSocket messages write to signals, UI reads them
- onMount + onCleanup — connect on mount, disconnect on unmount
- Immutable collections —
new Map(old)for reactive updates - computed() —
stockListderives a sorted array from the Map signal - Reactive classes — CSS classes that change based on signal values
Next Steps
Section titled “Next Steps”- Lifecycle — Deep dive into onMount and onCleanup
- Dashboard — Data fetching with resource()
- Data Fetching — HTTP-based data loading