Use cases
Ideas and patterns for where Fox Events fits: micro-frontends, wizards, dashboards, games, e-commerce, design systems, and more.
Micro-frontends and shells
Multiple apps or fragments need to talk without tight coupling. Use a shared event map and, if you need isolation, Fox.forScope.
// Shell emits: "shell:navigate", "shell:theme-changed"
// Fragment A emits: "cart:updated", "checkout:started"
// Fragment B listens to cart:updated and updates the badge
type ShellEvents = {
"shell:navigate": { path: string };
"shell:theme-changed": { theme: "light" | "dark" };
"cart:updated": { itemCount: number };
"checkout:started": void;
};
Fox.on<ShellEvents>("cart:updated", (p) => updateCartBadge(p.itemCount));
Fox.emit<ShellEvents>("shell:navigate", { path: "/checkout" });Each micro-frontend can use its own scope so events don’t leak; the shell can still emit global events for navigation or theme.
---
Wizards and multi-step flows
Steps emit completion; the wizard (or a central coordinator) listens and advances. Typed payloads keep “step 2 result” clear.
type WizardEvents = {
"wizard:step-complete": { stepId: string; data: Record<string, unknown> };
"wizard:back": { fromStep: string };
"wizard:abort": void;
};
// Step 2 form
Fox.emit<WizardEvents>("wizard:step-complete", {
stepId: "payment",
data: { method: "card", last4: "4242" },
});
// Wizard container
Fox.on<WizardEvents>("wizard:step-complete", (p) => {
saveStepData(p.stepId, p.data);
goToNextStep(p.stepId);
});Use once for “wait until user completes step” in async flows.
---
Real-time dashboards and live data
Feeds (WebSocket, polling, SSE) emit normalized events; widgets subscribe only to what they need. Trail helps debug “who received what.”
type DashboardEvents = {
"feed:price": { symbol: string; price: number; ts: number };
"feed:alert": { severity: "info" | "warn" | "error"; message: string };
"dashboard:filter-changed": { filter: string };
};
// Price widget
Fox.on<DashboardEvents>("feed:price", (p) => {
if (p.symbol === "BTC") updateBtcPrice(p.price);
});
// When replaying or debugging
const history = Fox.channel<DashboardEvents["feed:price"]>("feed:price").trail();
console.log(history.toText());---
Game UI: scores, achievements, levels
Game logic emits; UI layer listens and animates. Typed events avoid magic strings and keep payloads (e.g. score delta, achievement id) clear.
type GameEvents = {
"game:score": { delta: number; total: number };
"game:achievement": { id: string; name: string };
"game:level-complete": { level: number; stars: number };
"game:over": { score: number; reason: string };
};
Fox.emit<GameEvents>("game:achievement", { id: "first_blood", name: "First Blood" });
Fox.on<GameEvents>("game:score", (p) => animateScore(p.total));
Fox.once<GameEvents>("game:level-complete").then((p) => showLevelCompleteModal(p.stars));---
E-commerce: cart, checkout, inventory
Cart service emits cart:item-added, cart:item-removed; header, mini-cart, and checkout listen. Optional storage can persist cart events for refresh/hydrate.
type CartEvents = {
"cart:item-added": { sku: string; qty: number; name: string };
"cart:item-removed": { sku: string };
"cart:updated": { itemCount: number; total: number };
"checkout:started": void;
"checkout:completed": { orderId: string };
};
Fox.emit<CartEvents>("cart:item-added", { sku: "SHIRT-1", qty: 2, name: "T-Shirt" });
Fox.on<CartEvents>("cart:updated", (p) => updateHeaderBadge(p.itemCount));---
Design systems and theme/layout toggles
Theme or layout changes broadcast once; every component that cares subscribes. No prop drilling.
type DesignSystemEvents = {
"ds:theme": { theme: "light" | "dark" | "system" };
"ds:sidebar": { open: boolean };
"ds:density": { density: "compact" | "comfortable" };
};
Fox.emit<DesignSystemEvents>("ds:theme", { theme: "dark" });
Fox.on<DesignSystemEvents>("ds:theme", (p) => document.documentElement.setAttribute("data-theme", p.theme));---
React Native + WebView hybrid
RN sends commands into the WebView; the web app emits “ready”, “login”, etc. back. Same event names and types on both sides. Set up the bridge on the web with createReactNativeBridge and on RN with createFoxRNAdapter + WebView onMessage / inject.
1. Web (inside WebView) — setup bridge, listen for RN commands, emit to RN
import { Fox } from "fox-events";
import { createReactNativeBridge } from "fox-events/bridge-react-native";
createReactNativeBridge({ direction: "both", filter: (n) => n.startsWith("app:") });
Fox.on("app:command", (payload) => {
if (payload.action === "reload") window.location.reload();
if (payload.action === "back") history.back();
});
Fox.emit("app:ready", { version: "1.0" });
Fox.emit("app:login", { userId: "u-1", email: "user@example.com" });2. React Native — adapter + WebView, send commands, listen for web events
import { useRef, useMemo } from "react";
import { WebView } from "react-native-webview";
import { createFoxRNAdapter } from "fox-events/bridge-react-native";
function App() {
const webViewRef = useRef<WebView>(null);
const fox = useMemo(
() =>
createFoxRNAdapter({
sendToWebView: (msg) => {
const code = `window.postMessage(${JSON.stringify(msg)}, '*'); true;`;
webViewRef.current?.injectJavaScript(code);
},
filter: (name) => name.startsWith("app:"),
}),
[]
);
return (
<WebView
ref={webViewRef}
source={{ uri: "https://example.com" }}
onMessage={(e) => fox.handleMessage(e.nativeEvent.data)}
/>
);
}
// Usage: send command to web, listen for events from web
fox.emit("app:command", { action: "reload" });
fox.on("app:ready", (p) => setWebReady(true));
fox.on("app:login", (p) => setUser(p.userId, p.email));
const loginPayload = await fox.once("app:login");Define one event map and use it in both environments for a consistent contract.
---
Analytics and audit trail
Emit every meaningful action as an event; one listener sends to your analytics or audit log. Trail gives you a local trace for debugging.
type AuditEvents = {
"audit:action": { action: string; payload: unknown; userId: string; ts: number };
};
Fox.on<AuditEvents>("audit:action", (p) => sendToAnalytics(p));
// Any feature
Fox.emit<AuditEvents>("audit:action", {
action: "checkout.completed",
payload: { orderId: "123" },
userId: "u-1",
ts: Date.now(),
});---
Plugin and extension lifecycle
Host app emits “app:ready”, “route:changed”, “user:logged-in”; plugins subscribe and run their logic. Typed events make the contract clear for third-party code.
type HostEvents = {
"app:ready": void;
"route:changed": { path: string; params: Record<string, string> };
"user:logged-in": { userId: string; role: string };
};
// Plugin
Fox.on<HostEvents>("route:changed", (p) => {
if (p.path.startsWith("/admin")) loadAdminPlugin();
});---
A/B tests and feature flags
Emit “experiment:exposed” or “feature:checked” with variant id; analytics or logging listen. No need to pass flags through every component.
type ExperimentEvents = {
"experiment:exposed": { experimentId: string; variant: string };
"feature:checked": { name: string; enabled: boolean };
};
Fox.emit<ExperimentEvents>("experiment:exposed", { experimentId: "checkout-v2", variant: "B" });
Fox.on<ExperimentEvents>("experiment:exposed", (p) => trackExposure(p.experimentId, p.variant));---
Notifications and toasts
A single channel for “show toast”; any part of the app emits, the notification component listens and queues.
const toasts = Fox.channel<{ id: string; message: string; type: "info" | "success" | "error"; duration?: number }>("ui:toast");
toasts.emit({ id: crypto.randomUUID(), message: "Saved!", type: "success", duration: 3000 });
toasts.on((t) => toastQueue.add(t));---
Undo / redo and command history
Emit commands as events; a central handler pushes to a history stack and applies inverse actions. Trail can complement for debugging.
type CommandEvents = {
"command:execute": { type: string; payload: unknown; undo: () => void };
};
Fox.on<CommandEvents>("command:execute", (p) => {
commandHistory.push(p);
p.undo(); // or apply, then store undo
});
Fox.emit<CommandEvents>("command:execute", {
type: "rename",
payload: { id: "doc-1", name: "New Name" },
undo: () => revertRename("doc-1"),
});---
Pick the events and payloads that match your domain; use foxes.ts to keep them in one place and typed end-to-end.