Subscriptions
Subscriptions let you react when flag values change. Register a callback, and it fires whenever the store updates. This enables reactive UIs, logging, and side effects.
How It Works
When you call subscribe(), you register a callback function. Every time a flag changes (via set(), toggle(), increment(), etc.), your callback receives the key that changed plus the old and new values.
store.set('gold', 100)
↓
┌───────────────────────┐
│ Flag Store │
│ gold: 50 → 100 │
└───────────────────────┘
↓
Notify subscribers
↓
callback('gold', 100, 50)
Basic Usage
import { createFlagStore } from '@motioneffector/flags'
const store = createFlagStore({
initial: { gold: 0, health: 100 }
})
// Subscribe to all changes
const unsubscribe = store.subscribe((key, newValue, oldValue) => {
console.log(`${key}: ${oldValue} -> ${newValue}`)
})
store.set('gold', 50) // Logs: "gold: 0 -> 50"
store.increment('gold', 25) // Logs: "gold: 50 -> 75"
// Stop receiving notifications
unsubscribe()
store.set('gold', 100) // No log (unsubscribed)
Key Points
Returns an unsubscribe function - Call it to stop receiving notifications. Safe to call multiple times.
Callbacks receive (key, newValue, oldValue) - For new keys,
oldValueisundefined. For deleted keys,newValueisundefined.Errors in callbacks are caught - If your callback throws, it's logged to
console.errorbut doesn't break the store or other subscribers.Multiple subscribers are supported - All subscribers receive notifications in the order they were registered.
Key-Specific Subscriptions
Use subscribeKey() to watch a single key:
// Only fires when 'gold' changes
store.subscribeKey('gold', (newValue, oldValue) => {
console.log(`Gold: ${oldValue} -> ${newValue}`)
})
store.set('gold', 100) // Logs: "Gold: 0 -> 100"
store.set('health', 50) // No log (different key)
Note that subscribeKey() callbacks receive only (newValue, oldValue), not the key.
Examples
UI Updates
// Update a health bar when health changes
store.subscribeKey('health', (newValue) => {
const healthBar = document.querySelector('.health-bar')
healthBar.style.width = `${newValue}%`
})
Logging
// Log all state changes for debugging
store.subscribe((key, newValue, oldValue) => {
console.log(`[State] ${key}: ${JSON.stringify(oldValue)} -> ${JSON.stringify(newValue)}`)
})
Computed Side Effects
// Trigger game over when health reaches 0
store.subscribeKey('health', (newValue) => {
if (newValue <= 0) {
showGameOverScreen()
}
})
// Play sound when gold increases
store.subscribeKey('gold', (newValue, oldValue) => {
if (newValue > (oldValue ?? 0)) {
playSound('coin-collect')
}
})
Managing Multiple Subscriptions
const unsubscribers: Array<() => void> = []
// Collect unsubscribe functions
unsubscribers.push(
store.subscribeKey('gold', updateGoldUI),
store.subscribeKey('health', updateHealthUI),
store.subscribe(logAllChanges)
)
// Later, clean up all subscriptions
function cleanup() {
unsubscribers.forEach(unsub => unsub())
}
Batch Operations
When you use batch(), subscribers are notified once at the end, not for each individual change:
store.subscribe((key) => {
console.log(`Changed: ${key}`)
})
store.batch(() => {
store.set('a', 1)
store.set('b', 2)
store.set('c', 3)
})
// Logs once: "Changed: __batch__"
// Not three times
This prevents unnecessary re-renders when making multiple related changes.
Related
- Flag Store - Where changes originate
- Computed Flags - Derived values that also trigger subscriptions
- Batch Operations - Group changes for single notification
- API: Subscriptions - Full method reference