Examples
Store Dependencies
Store Dependencies
When your application grows, you'll often need multiple stores that react to each other's events. The Event Bus enables this by routing events between connected stores — keeping them decoupled.
The Pattern
The inventory store doesn't know about the cart. The cart store doesn't know about inventory. The event bus wires them together.
Example: Shop with Inventory & Cart
1. Define the stores
import {
createStore,
createCommand,
createEvent,
sealStore,
} from "@naikidev/commiq";
// Events shared between stores
const stockReserved = createEvent<{ productId: number; qty: number }>(
"stockReserved",
);
const outOfStock = createEvent<{ productId: number }>("outOfStock");
// ── Inventory Store ──
const _inventoryStore = createStore<InventoryState>({
products: [
{ id: 1, name: "Keyboard", price: 79, stock: 3 },
{ id: 2, name: "USB-C Hub", price: 45, stock: 5 },
],
});
_inventoryStore.addCommandHandler<{ productId: number; qty: number }>(
"reserveStock",
(ctx, cmd) => {
const { productId, qty } = cmd.data;
const product = ctx.state.products.find((p) => p.id === productId);
if (!product || product.stock < qty) {
ctx.emit(outOfStock, { productId });
return;
}
ctx.setState({
products: ctx.state.products.map((p) =>
p.id === productId ? { ...p, stock: p.stock - qty } : p,
),
});
ctx.emit(stockReserved, { productId, qty });
},
);
export const inventoryStore = sealStore(_inventoryStore);
// ── Cart Store ──
const _cartStore = createStore<CartState>({ items: [], lastError: "" });
_cartStore.addCommandHandler<{
productId: number;
name: string;
price: number;
}>("addToCart", (ctx, cmd) => {
ctx.setState({
...ctx.state,
items: [...ctx.state.items, { ...cmd.data, qty: 1 }],
});
});
export const cartStore = sealStore(_cartStore);2. Wire with Event Bus
import { createEventBus, createCommand } from "@naikidev/commiq";
const bus = createEventBus();
bus.connect(_inventoryStore);
bus.connect(_cartStore);
// When stock is reserved → add item to cart
bus.on(stockReserved, (event) => {
const product = _inventoryStore.state.products.find(
(p) => p.id === event.data.productId,
);
if (product) {
_cartStore.queue(
createCommand("addToCart", {
productId: product.id,
name: product.name,
price: product.price,
}),
);
}
});
// When out of stock → show error
bus.on(outOfStock, () => {
_cartStore.queue(createCommand("setError", "Item is out of stock"));
});3. Use in React
function ShopPage() {
const products = useSelector(inventoryStore, (s) => s.products);
const cart = useSelector(cartStore, (s) => s.items);
const queueInventory = useQueue(inventoryStore);
return (
<>
{products.map((p) => (
<button
key={p.id}
disabled={p.stock === 0}
onClick={() =>
queueInventory(
createCommand("reserveStock", { productId: p.id, qty: 1 }),
)
}
>
{p.name} ({p.stock} left)
</button>
))}
<p>Cart: {cart.length} items</p>
</>
);
}Key Takeaways
- Stores stay decoupled — they only know about their own commands and events
- The event bus is the glue — it subscribes to streams and routes events to commands
- Events are shared via imports — both stores reference the same
EventDefobjects (matched by symbol identity) - Unidirectional flow — Command → Handler → Event → Bus → Command on another store