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