Casos de uso
Ideias e padrões para onde o Fox Events se encaixa: micro-frontends, wizards, dashboards, jogos, e-commerce, design systems e mais.
Micro-frontends e shells
Vários apps ou fragmentos precisam se comunicar sem acoplamento. Use um mapa de eventos compartilhado e, se precisar de isolamento, Fox.forScope.
// Shell emite: "shell:navigate", "shell:theme-changed"
// Fragmento A emite: "cart:updated", "checkout:started"
// Fragmento B escuta cart:updated e atualiza o 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" });Cada micro-frontend pode usar seu próprio escopo para eventos não vazarem; o shell ainda emite eventos globais para navegação ou tema.
---
Wizards e fluxos multi-etapa
Etapas emitem conclusão; o wizard (ou um coordenador) escuta e avança. Payloads tipados deixam “resultado da etapa 2” explícito.
type WizardEvents = {
"wizard:step-complete": { stepId: string; data: Record<string, unknown> };
"wizard:back": { fromStep: string };
"wizard:abort": void;
};
// Form da etapa 2
Fox.emit<WizardEvents>("wizard:step-complete", {
stepId: "payment",
data: { method: "card", last4: "4242" },
});
// Container do wizard
Fox.on<WizardEvents>("wizard:step-complete", (p) => {
saveStepData(p.stepId, p.data);
goToNextStep(p.stepId);
});Use once para “esperar até o usuário concluir a etapa” em fluxos assíncronos.
---
Dashboards em tempo real e dados ao vivo
Feeds (WebSocket, polling, SSE) emitem eventos normalizados; widgets inscrevem só no que precisam. O Trail ajuda a debugar “quem recebeu o quê”.
type DashboardEvents = {
"feed:price": { symbol: string; price: number; ts: number };
"feed:alert": { severity: "info" | "warn" | "error"; message: string };
"dashboard:filter-changed": { filter: string };
};
// Widget de preço
Fox.on<DashboardEvents>("feed:price", (p) => {
if (p.symbol === "BTC") updateBtcPrice(p.price);
});
// Ao replayed ou debugar
const history = Fox.channel<DashboardEvents["feed:price"]>("feed:price").trail();
console.log(history.toText());---
UI de jogo: placar, conquistas, níveis
A lógica do jogo emite; a camada de UI escuta e anima. Eventos tipados evitam strings mágicas e deixam payloads (ex.: delta de pontos, id da conquista) claros.
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: carrinho, checkout, estoque
O serviço de carrinho emite cart:item-added, cart:item-removed; header, mini-cart e checkout escutam. Storage opcional pode persistir eventos do carrinho para 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 e toggles de tema/layout
Mudanças de tema ou layout são emitidas uma vez; todo componente interessado se inscreve. Sem 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));---
Híbrido React Native + WebView
RN envia comandos para a WebView; o app web emite “ready”, “login”, etc. de volta. Mesmos nomes e tipos de evento nos dois lados. Configure o bridge no web com createReactNativeBridge e no RN com createFoxRNAdapter + onMessage / inject da WebView.
1. Web (dentro da WebView) — configurar bridge, ouvir comandos do RN, emitir para o 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, enviar comandos, ouvir eventos da web
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)}
/>
);
}
// Uso: enviar comando para a web, ouvir eventos da 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");Defina um mapa de eventos e use nos dois ambientes para um contrato consistente.
---
Analytics e trilha de auditoria
Emita toda ação relevante como evento; um listener envia para seu analytics ou log de auditoria. O Trail dá um rastro local para debug.
type AuditEvents = {
"audit:action": { action: string; payload: unknown; userId: string; ts: number };
};
Fox.on<AuditEvents>("audit:action", (p) => sendToAnalytics(p));
// Qualquer feature
Fox.emit<AuditEvents>("audit:action", {
action: "checkout.completed",
payload: { orderId: "123" },
userId: "u-1",
ts: Date.now(),
});---
Lifecycle de plugins e extensões
O app host emite “app:ready”, “route:changed”, “user:logged-in”; plugins se inscrevem e rodam sua lógica. Eventos tipados deixam o contrato claro para código de terceiros.
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 e feature flags
Emita “experiment:exposed” ou “feature:checked” com id da variante; analytics ou logging escutam. Sem precisar passar flags por todo componente.
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));---
Notificações e toasts
Um único canal para “mostrar toast”; qualquer parte do app emite, o componente de notificação escuta e enfileira.
const toasts = Fox.channel<{ id: string; message: string; type: "info" | "success" | "error"; duration?: number }>("ui:toast");
toasts.emit({ id: crypto.randomUUID(), message: "Salvo!", type: "success", duration: 3000 });
toasts.on((t) => toastQueue.add(t));---
Undo / redo e histórico de comandos
Emita comandos como eventos; um handler central empilha no histórico e aplica ações inversas. O Trail pode complementar para debug.
type CommandEvents = {
"command:execute": { type: string; payload: unknown; undo: () => void };
};
Fox.on<CommandEvents>("command:execute", (p) => {
commandHistory.push(p);
p.undo(); // ou apply, depois guardar undo
});
Fox.emit<CommandEvents>("command:execute", {
type: "rename",
payload: { id: "doc-1", name: "Novo Nome" },
undo: () => revertRename("doc-1"),
});---
Escolha os eventos e payloads que combinam com seu domínio; use foxes.ts para mantê-los em um lugar e tipados de ponta a ponta.