HookState Interface
Overview
Section titled “Overview”The HookState<T> interface is the core contract returned by all hooks in Phaser Hooks. It provides a consistent API for getting, setting, and observing state changes.
interface HookState<T> { get(): T; set(value: T): void; on(event: 'change', callback: (newValue: T, oldValue: T) => void): () => void; once(event: 'change', callback: (newValue: T, oldValue: T) => void): () => void; off(event: 'change', callback: (newValue: T, oldValue: T) => void): void; clearListeners(): void;}Methods
Section titled “Methods”Retrieves the current state value.
get(): TReturns
Section titled “Returns”T - The current state value
Example
Section titled “Example”const playerState = withLocalState(this, 'player', { hp: 100, level: 1 });
const currentPlayer = playerState.get();console.log(currentPlayer.hp); // 100console.log(currentPlayer.level); // 1set(value)
Section titled “set(value)”Updates the state value and triggers all registered change listeners.
set(value: T): voidParameters
Section titled “Parameters”| Parameter | Type | Description |
|---|---|---|
value | T | New state value |
Throws
Section titled “Throws”- ValidationError - If a validator is configured and the value fails validation
Example
Section titled “Example”const playerState = withLocalState(this, 'player', { hp: 100, level: 1 });
// Update stateplayerState.set({ hp: 90, level: 1 });
// Use spread operator for partial updatesplayerState.set({ ...playerState.get(), hp: playerState.get().hp - 10,});With Validation
Section titled “With Validation”const healthState = withLocalState(this, 'health', 100, { validator: (value) => { if (value < 0) return 'Health cannot be negative'; return true; },});
try { healthState.set(-10); // Throws error} catch (error) { console.error(error.message); // "Health cannot be negative"}
healthState.set(90); // Valid - no erroron(event, callback)
Section titled “on(event, callback)”Registers a callback function that fires whenever the state changes.
on(event: 'change', callback: (newValue: T, oldValue: T) => void): () => voidParameters
Section titled “Parameters”| Parameter | Type | Description |
|---|---|---|
event | 'change' | Event type (currently only ‘change’ is supported) |
callback | (newValue: T, oldValue: T) => void | Function to call on state change |
Returns
Section titled “Returns”() => void - Unsubscribe function to remove this listener
Example
Section titled “Example”const playerState = withLocalState(this, 'player', { hp: 100 });
// Subscribe to changesconst unsubscribe = playerState.on('change', (newPlayer, oldPlayer) => { console.log('HP changed from', oldPlayer.hp, 'to', newPlayer.hp);
if (newPlayer.hp <= 0) { console.log('Game Over!'); }});
playerState.set({ hp: 90 });// Logs: "HP changed from 100 to 90"
// Later, unsubscribeunsubscribe();
playerState.set({ hp: 80 });// No log - listener was removedMultiple Listeners
Section titled “Multiple Listeners”const playerState = withLocalState(this, 'player', { hp: 100, mp: 50 });
// First listener - tracks HPconst unsubHP = playerState.on('change', (newPlayer) => { console.log('Current HP:', newPlayer.hp);});
// Second listener - tracks MPconst unsubMP = playerState.on('change', (newPlayer) => { console.log('Current MP:', newPlayer.mp);});
playerState.set({ hp: 90, mp: 40 });// Logs both:// "Current HP: 90"// "Current MP: 40"
// Unsubscribe individuallyunsubHP();unsubMP();once(event, callback)
Section titled “once(event, callback)”Registers a callback that fires only once on the next state change, then automatically unsubscribes.
once(event: 'change', callback: (newValue: T, oldValue: T) => void): () => voidParameters
Section titled “Parameters”| Parameter | Type | Description |
|---|---|---|
event | 'change' | Event type (currently only ‘change’ is supported) |
callback | (newValue: T, oldValue: T) => void | Function to call on state change |
Returns
Section titled “Returns”() => void - Unsubscribe function (can be used to cancel before it fires)
Example
Section titled “Example”const playerState = withLocalState(this, 'player', { level: 1 });
// This will only fire onceplayerState.once('change', (newPlayer) => { console.log('First level up detected!', newPlayer.level);});
playerState.set({ level: 2 }); // Fires callback// Logs: "First level up detected! 2"
playerState.set({ level: 3 }); // Does NOT fire callback// No logCanceling a One-Time Listener
Section titled “Canceling a One-Time Listener”const playerState = withLocalState(this, 'player', { hp: 100 });
const unsubscribe = playerState.once('change', () => { console.log('This might never fire');});
// Cancel before it firesunsubscribe();
playerState.set({ hp: 90 }); // No callback firesoff(event, callback)
Section titled “off(event, callback)”Removes a specific event listener.
off(event: 'change', callback: (newValue: T, oldValue: T) => void): voidParameters
Section titled “Parameters”| Parameter | Type | Description |
|---|---|---|
event | 'change' | Event type (currently only ‘change’ is supported) |
callback | (newValue: T, oldValue: T) => void | The exact same function instance passed to on() |
Example
Section titled “Example”export class GameScene extends Phaser.Scene { private healthCallback: (newPlayer: any, oldPlayer: any) => void;
create() { const playerState = withLocalState(this, 'player', { hp: 100 });
// Store callback as property this.healthCallback = (newPlayer, oldPlayer) => { console.log('HP changed:', newPlayer.hp); };
// Subscribe playerState.on('change', this.healthCallback);
// Later, unsubscribe playerState.off('change', this.healthCallback); }}Common Mistake
Section titled “Common Mistake”const playerState = withLocalState(this, 'player', { hp: 100 });
// DON'T DO THIS - won't work!playerState.on('change', (player) => { console.log('HP:', player.hp);});
// This won't remove the listener because it's a different function instanceplayerState.off('change', (player) => { console.log('HP:', player.hp);});
// INSTEAD, use the unsubscribe function returned by .on():const unsubscribe = playerState.on('change', (player) => { console.log('HP:', player.hp);});
// Later:unsubscribe(); // This works!clearListeners()
Section titled “clearListeners()”Removes all event listeners for this state.
clearListeners(): voidExample
Section titled “Example”export class GameScene extends Phaser.Scene { private playerState: HookState<{ hp: number }>;
create() { this.playerState = withLocalState(this, 'player', { hp: 100 });
// Add multiple listeners this.playerState.on('change', () => console.log('Listener 1')); this.playerState.on('change', () => console.log('Listener 2')); this.playerState.on('change', () => console.log('Listener 3')); }
shutdown() { // Clear all listeners at once this.playerState.clearListeners(); }}Use Cases
Section titled “Use Cases”- Scene cleanup - Remove all listeners when transitioning scenes
- Global state - Essential for cleaning up global state listeners
- Reset state - Clear all listeners before re-initializing
Global State Cleanup
Section titled “Global State Cleanup”export class GameScene extends Phaser.Scene { private globalSettings: HookState<GameSettings>;
create() { this.globalSettings = withGlobalState(this, 'settings', defaultSettings);
this.globalSettings.on('change', (settings) => { console.log('Settings updated:', settings); });
// IMPORTANT: Clean up global state when scene is destroyed this.events.once('destroy', () => { this.globalSettings.clearListeners(); }); }}Complete Usage Example
Section titled “Complete Usage Example”import { Scene } from 'phaser';import { withLocalState } from 'phaser-hooks';
export class GameScene extends Scene { private playerState: HookState<PlayerData>; private unsubscribeFns: (() => void)[] = [];
create() { // Initialize state this.playerState = withLocalState(this, 'player', { hp: 100, maxHp: 100, level: 1, exp: 0, });
// Get current value const player = this.playerState.get(); console.log('Initial HP:', player.hp);
// Subscribe to changes const unsubHP = this.playerState.on('change', (newPlayer, oldPlayer) => { if (newPlayer.hp !== oldPlayer.hp) { this.updateHealthBar(newPlayer.hp, newPlayer.maxHp); } }); this.unsubscribeFns.push(unsubHP);
// One-time listener for first level up this.playerState.once('change', (newPlayer, oldPlayer) => { if (newPlayer.level > oldPlayer.level) { console.log('First level up!'); } });
// Update state this.playerState.set({ ...this.playerState.get(), hp: 90, }); }
updateHealthBar(hp: number, maxHp: number) { console.log(`HP: ${hp}/${maxHp}`); }
shutdown() { // Clean up all listeners this.unsubscribeFns.forEach(fn => fn()); this.unsubscribeFns = []; }}Next Steps
Section titled “Next Steps”- API Reference - Complete API documentation
- Event Management - Learn about event management
- Getting Started - Quick start guide