Skip to content

Phaser Hooks

Bring React-like hooks pattern to your Phaser 3 games with simple, powerful state management

Phaser gives you registry (global) and data (local) for state management. They work fine, but the API is verbose and error-prone:

// Phaser's built-in way - this == scene
this.game.registry.set('volume', 0.5);
const volume = this.game.registry.get('volume');
this.game.registry.events.on('changedata-volume', (game, value) => {
console.log('Volume changed to', value);
});
this.data.set('score', 42);
const score = this.data.get('score');
this.onChangeFn = (scene, value) => {
console.log('Score updated to', value);
};
this.data.events.on('changedata-score', this.onChangeFn); // if you pass an anonymous function, you cannot unsubscribe
// when move to another scene, you must unsubscribe. Boring and easy to forget
this.data.events.off('changedata-score', this.onChangeFn);

With phaser-hooks, you get a simple, React-like API:

const volume = withGlobalState(this, 'volume', 0.5);
volume.get(); // Returns: 0.5
volume.set(0.8); // updates the value
this.unsubscribe = volume.on('change', () => {
console.log('Volume changed →', volume.get())
}); // Returns the easy unsubscribe function
// when changing scenes
this.unsubscribe();
  • React-like patterns - Hooks work just like React: same key = same state
  • Type-safe - Full TypeScript support with inference
  • Memory safe - Auto-cleanup prevents memory leaks
  • Feature-rich - Persistence, computed state, undo/redo, validation
  • Familiar - React-like patterns for easier onboarding

React-like API

Familiar hooks pattern inspired by React’s useState, useEffect, and more

Multiple Hook Types

Local, global, persistent, computed, undoable, and debounced state management

TypeScript First

Full TypeScript support with complete type inference and safety

Automatic Cleanup

Built-in scene cleanup to prevent memory leaks

Validation

Built-in validators for common patterns plus custom validation support

Debug Mode

Comprehensive logging for development and troubleshooting

hooks/withPlayerState.ts
import { withLocalState } from 'phaser-hooks';
// or const { withLocalState } from 'phaser-hooks';
const withPlayer = (scene: Phaser.Scene) => {
const player = withLocalState(scene, 'player', {
hp: 100,
maxHp: 100,
level: 1,
});
return player;
};
// hooks/withSettings.ts
import { withGlobalState } from 'phaser-hooks';
const withSettings = (scene: Phaser.Scene) => {
const settings = withGlobalState(scene, 'settings', {
volume: 0.8,
difficulty: 'normal'
});
return settings;
};
// scenes/gameScene.ts
import { withLocalState, withGlobalState } from 'phaser-hooks';
class GameScene extends Phaser.Scene {
private unsubscribe?: () => void;
create() {
// 1. Local state (scene-specific, auto-cleanup)
const player = withPlayer(this); // clean and reusable within the same scene
// 2. Global state (persists across scenes)
const settings = withSettings(this); // the same instance in all scenes
// 3. Update state
player.patch({ hp: 90 }); // Partial update
settings.set({ volume: 0.5, difficulty: 'hard' }); // Full update
// 4. Read state
console.log(player.get().hp); // 90
console.log(settings.get().volume); // 0.5
// 5. Listen to changes
this.unsubscribe = player.on('change', (newPlayer, oldPlayer) => {
if (newPlayer.hp < 20) {
console.warn(`Low health! Your old HP was ${oldPlayer.hp}`);
}
});
}
shutdown() {
// 6. Clean up (local state auto-cleans, but it’s good practice)
this.unsubscribe?.();
}
}
  • withLocalState - Scene-specific state with automatic cleanup
  • withGlobalState - Application-wide state across all scenes
  • withPersistentState - State with localStorage/sessionStorage persistence
  • withComputedState - Derived state that auto-updates from source state
  • withUndoableState - State with built-in undo/redo functionality
  • withDebouncedState - State with debounced updates

While React hooks use the “use” prefix, this library uses “with” to avoid linting issues. ESLint’s hooks rules expect “use” functions only in React components and .jsx/.tsx files. Since Phaser games typically use plain TypeScript/JavaScript, the “with” prefix prevents false positives while maintaining clear, consistent naming.