With Local State
The withLocalState hook provides scene-specific state management that automatically cleans up when the scene is destroyed. It’s perfect for temporary state that should reset when switching scenes—like player health, enemy positions, or UI state.
How It Works
Section titled “How It Works”Singleton Pattern
Section titled “Singleton Pattern”Just like React hooks, withLocalState uses keys as singleton references:
- First call: Creates the state with the initial value
- Subsequent calls: Returns the existing state (initial value is ignored)
- Same key = same state instance across your entire scene
This means you can call the same hook from:
- ✅ Your scene’s create()method
- ✅ Child components (GameObjects, Containers)
- ✅ Systems and managers
- ✅ Event handlers
They all share the same state!
class GameScene extends Phaser.Scene {  create(): void {    const player1 = withLocalState(this, 'player', { hp: 100 });    player1.set({ hp: 50 });
    // Later, somewhere else in the scene...    const player2 = withLocalState(this, 'player', { hp: 999 }); // Initial value ignored!    console.log(player2.get().hp); // 50 (not 999!)  }}class GameScene extends Phaser.Scene {  create() {    const player1 = withLocalState(this, 'player', { hp: 100 });    player1.set({ hp: 50 });
    // Later, somewhere else in the scene...    const player2 = withLocalState(this, 'player', { hp: 999 }); // Initial value ignored!    console.log(player2.get().hp); // 50 (not 999!)  }}Automatic Cleanup
Section titled “Automatic Cleanup”When the scene is destroyed (or you call this.scene.stop()), withLocalState automatically:
- ✅ Removes all event listeners
- ✅ Clears the state data
- ✅ Prevents memory leaks
No manual cleanup needed! (Though you can still call clearListeners() if you want.)
Creating Custom Hooks (Recommended)
Section titled “Creating Custom Hooks (Recommended)”Instead of repeating the key and initial state everywhere, create a custom hook:
import { type HookState, withLocalState } from 'phaser-hooks';
type Player = {  hp: number;  maxHp: number;  level: number;  exp: number;};
type PlayerState = HookState<Player> & {  takeDamage: (amount: number) => void;  heal: (amount: number) => void;  levelUp: () => void;};
export function withPlayer(scene: Phaser.Scene): PlayerState {  const state = withLocalState<Player>(scene, 'player', {    hp: 100,    maxHp: 100,    level: 1,    exp: 0,  });
  return {    ...state,
    takeDamage: (amount: number) => {      const current = state.get();      state.patch({ hp: Math.max(0, current.hp - amount) });    },
    heal: (amount: number) => {      const current = state.get();      state.patch({ hp: Math.min(current.maxHp, current.hp + amount) });    },
    levelUp: () => {      const current = state.get();      state.patch({        level: current.level + 1,        maxHp: current.maxHp + 10,        hp: current.maxHp + 10,        exp: 0,      });    },  };}const { withLocalState } = require('phaser-hooks');
function withPlayer(scene) {  const state = withLocalState(scene, 'player', {    hp: 100,    maxHp: 100,    level: 1,    exp: 0,  });
  return {    ...state,
    takeDamage: (amount) => {      const current = state.get();      state.patch({ hp: Math.max(0, current.hp - amount) });    },
    heal: (amount) => {      const current = state.get();      state.patch({ hp: Math.min(current.maxHp, current.hp + amount) });    },
    levelUp: () => {      const current = state.get();      state.patch({        level: current.level + 1,        maxHp: current.maxHp + 10,        hp: current.maxHp + 10,        exp: 0,      });    },  };}
module.exports = { withPlayer };Usage Example
Section titled “Usage Example”Now you can use withPlayer anywhere in your scene:
import { withPlayer } from './hooks/withPlayer';
class GameScene extends Phaser.Scene {  create() {    const player = withPlayer(this);
    // Use clean API    player.takeDamage(30);    player.heal(10);    player.levelUp();
    // Listen to changes    player.on('change', (newPlayer, oldPlayer) => {      console.log(`HP: ${newPlayer.hp}/${newPlayer.maxHp}`);
      if (newPlayer.hp === 0) {        this.scene.start('GameOverScene');      }    });  }}
// HealthBar.ts - Access the SAME state!class HealthBar extends Phaser.GameObjects.Container {  constructor(scene: Phaser.Scene) {    super(scene, 0, 0);
    const player = withPlayer(scene); // Same player state!
    player.on('change', (newPlayer) => {      this.updateBar(newPlayer.hp, newPlayer.maxHp);    });  }
  updateBar(hp: number, maxHp: number) {    // Update visual representation  }}const { withPlayer } = require('./hooks/withPlayer');
class GameScene extends Phaser.Scene {  create() {    const player = withPlayer(this);
    player.takeDamage(30);    player.heal(10);
    player.on('change', (newPlayer, oldPlayer) => {      console.log(`HP: ${newPlayer.hp}/${newPlayer.maxHp}`);
      if (newPlayer.hp === 0) {        this.scene.start('GameOverScene');      }    });  }}
// HealthBar.jsclass HealthBar extends Phaser.GameObjects.Container {  constructor(scene) {    super(scene, 0, 0);
    const player = withPlayer(scene);
    player.on('change', (newPlayer) => {      this.updateBar(newPlayer.hp, newPlayer.maxHp);    });  }
  updateBar(hp, maxHp) {    // Update visual  }}Interactive Demo
Section titled “Interactive Demo”Try out the with local state example in the interactive demo below:
Click to load the interactive demo
Key Features
Section titled “Key Features”| Feature | Description | 
|---|---|
| Scene-specific | State exists only while the scene is active | 
| Automatic cleanup | Listeners and data removed when scene is destroyed | 
| Isolated | Each scene instance has its own independent state | 
| Singleton pattern | Same key returns the same state instance | 
| Type-safe | Full TypeScript support with type inference | 
When to Use
Section titled “When to Use”✅ Use withLocalState for:
- Player state in a gameplay scene
- Enemy AI state
- Level-specific data (current wave, score)
- Temporary UI state (menu selection)
- Game state that resets per scene
❌ Don’t use withLocalState for:
- Settings (use withGlobalState)
- User profile (use withPersistentState)
- Data that persists across scenes
function withLocalState<T>(  scene: Phaser.Scene,  key: string,  initialValue: T,  options?: {    debug?: boolean;    validator?: (value: T) => true | string;  }): HookState<T>Parameters
Section titled “Parameters”- scene- The Phaser scene instance
- key- Unique identifier for this state (singleton reference)
- initialValue- Initial state value (used only on first call)
- options(optional)- debug- Enable console logging for debugging
- validator- Validation function for state updates
 
Returns
Section titled “Returns”A HookState<T> object with methods: get(), set(), patch(), on(), once(), off(), clearListeners().
See Also
Section titled “See Also”- withGlobalState - For app-wide state
- Creating Custom Hooks - Deep dive into custom hooks
- State Keys & Singleton Pattern - Understanding how keys work
Next Steps
Section titled “Next Steps”- Learn about withGlobalState for cross-scene state
- Explore custom hooks patterns
- See real-world examples