Phaser Hooks vs MobX
Both Phaser Hooks and MobX are great tools for managing state, but they serve different worlds.
While MobX was built for UI frameworks like React or Vue, Phaser Hooks was designed specifically for Phaser’s game loop, giving you predictable, scene-aware reactivity with almost zero overhead.
This page provides an honest, developer-oriented comparison so you can choose what best fits your project.
Quick Comparison
Section titled “Quick Comparison”| Aspect | phaser-hooks | MobX | 
|---|---|---|
| Learning Curve | Low - Simple API | Medium - Concepts like observables, autorun, reactions | 
| Bundle Size | ~10KB | ~63KB | 
| TypeScript | First-class support | First-class support | 
| Boilerplate | Minimal | Minimal | 
| Reactivity | Controlled and explicit (ideal for game loops) | Automatic tracking in DOM, but in Phaser you must listen autorunmanually | 
| Phaser Integration | Native (registry + scene lifecycle) | External, requires manual cleanup | 
| Performance | Predictable, targeted updates | Optimized, but generalized | 
| Ecosystem | Purpose-built for Phaser | General-purpose (React, Vue, etc.) | 
Code Comparison
Section titled “Code Comparison”Simple Player State
Section titled “Simple Player State”import { type HookState, withLocalState } from 'phaser-hooks';
type Player = {  hp: number;  maxHp: number;  level: number;};
type PlayerState = HookState<Player> & {  takeDamage: (amount: number) => void;  heal: (amount: number) => void;  isDead: () => boolean;};
const initialState: Player = {  hp: 100,  maxHp: 100,  level: 1,};
export function withPlayer(scene: Phaser.Scene): PlayerState {  const state = withLocalState<Player>(scene, 'player', initialState);
  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) });    },
    isDead: () => state.get().hp <= 0,  };}
// Usage in sceneimport { withPlayer } from './hooks/withPlayer';
class GameScene extends Phaser.Scene {  private unsubscribe?: () => void;
  create() {    const player = withPlayer(this);
    // Update state    player.takeDamage(30);
    // Listen to changes (manual)    this.unsubscribe = player.on('change', (newPlayer) => {      console.log('HP:', newPlayer.hp);      this.updateHealthBar(newPlayer.hp, newPlayer.maxHp);
      if (player.isDead()) {        this.scene.start('GameOverScene');      }    });  }
  shutdown() {    this.unsubscribe?.();  }}import { makeAutoObservable } from 'mobx';
class PlayerStore {  hp = 100;  maxHp = 100;  level = 1;
  constructor() {    makeAutoObservable(this);  }
  takeDamage(amount: number) {    this.hp = Math.max(0, this.hp - amount);  }
  heal(amount: number) {    this.hp = Math.min(this.maxHp, this.hp + amount);  }
  get isDead() {    return this.hp <= 0;  }
  get healthPercent() {    return (this.hp / this.maxHp) * 100;  }}
export const playerStore = new PlayerStore();
// Usage in sceneimport { autorun } from 'mobx';import { playerStore } from './stores/PlayerStore';
class GameScene extends Phaser.Scene {  private disposer?: () => void;
  create() {    // Update state    playerStore.takeDamage(30);
    // Listen to changes (automatic tracking)    this.disposer = autorun(() => {      console.log('HP:', playerStore.hp);      this.updateHealthBar(playerStore.hp, playerStore.maxHp);
      if (playerStore.isDead) {        this.scene.start('GameOverScene');      }    });  }
  shutdown() {    this.disposer?.();  }}🎮 Reactivity Philosophy
Section titled “🎮 Reactivity Philosophy”Phaser Hooks: Designed for Game Loops
Section titled “Phaser Hooks: Designed for Game Loops”const player = withPlayer(this);player.on('change', (newValue, oldValue) => {  if (newValue.hp !== oldValue.hp) {    this.updateHealthBar(newValue.hp);  }});✅ Pros:
- Clear and predictable - you know exactly when callbacks fire, no hidden side effects
- Fine-grained control over what triggers updates
- Perfect for game loops where over-reactivity hurts FPS
- Automatic cleanup with scene shutdown in local state
- Easier debugging (no “why did this re-run?” surprises)
- No magic - straightforward mental model
- No need unsubscribe in local state
⚠️ Considerations:
- Need to call .get()to read state
- You cannot share the state with DOM elements in page, only in Phaser
MobX: Designed for UI Frameworks
Section titled “MobX: Designed for UI Frameworks”autorun(() => {  this.updateHealthBar(playerStore.hp);});✅ Pros:
- Automatic dependency tracking - no manual subscriptions
- Built-in computed properties
- Great for complex UI scenarios
⚠️ Considerations:
- Not designed for game loops - automatic tracking can be overkill
- Can be harder to debug (why did this re-run?)
- Potential for accidental over-reactivity
- Requires manual cleanup in Phaser scene shutdown
- Multiple different ways to implement the same thing (observers, decorators, classes, etc.)
🧠 API Surface
Section titled “🧠 API Surface”Phaser Hooks: Minimal and Game-Focused
state.get()      // Readstate.set(value) // Write (full)state.patch(partial) // Write (partial)state.on('change', callback) // Subscribe✅ Pros:
- Clean, predictable, and small
- Integrates directly with Phaser’s registry
- No decorators or special syntax
- Works seamlessly with Phaser’s existing patterns
- Functional programming friendly
⚠️ Considerations:
- Not meant for web UIs — 100% focused on game state
MobX: Feature-Rich for Apps
makeAutoObservable(this) // Make object reactiveautorun(() => {}); // Automatically tracks dependenciesreaction(() => {});computed get healthPercent() {};runInAction // Batch updates✅ Pros:
- Mature ecosytem and advanced patterns
- Powerful reaction APIs for complex scenarios
- Shared state between Phaser and React/Vue/Vanilla JS.
⚠️ Considerations:
- Larger API surface - more to learn
- Decorators can be confusing for beginners
- Requires understanding of observables/observers
- More complexity and concepts to learn
- Requires manual cleanup in Phaser scene shutdown
🔄 Integration with Phaser
Section titled “🔄 Integration with Phaser”phaser-hooks: Native Integration
// Uses Phaser's built-in registry under the hoodconst player = withLocalState<Player>(this, 'player', { hp: 100 });✅ Pros:
- Built from the ground up for Phaser
- Leverages Phaser’s registry system
- Automatic cleanup with scene lifecycle in local state
- No extra dependencies
- Smaller bundle size
⚠️ Considerations:
- Focused solely on Phaser (not intended for non-game apps)
MobX: Generic Integration
// Bring your own store managementimport { playerStore } from './stores/PlayerStore';
// Must manually integrate with Phaser lifecyclethis.events.once('shutdown', () => disposer());✅ Pros:
- Framework-agnostic - use in React, Vue, vanilla JS, etc.
- Can be shared across app layers (e.g. React + Phaser)
⚠️ Considerations:
- Additional dependency (~63KB)
- Requires manual cleanup
- Doesn’t understand Phaser’s scene lifecycle
🚀 Performance
Section titled “🚀 Performance”Both are fast, but their optimizations target different needs.
- Phaser Hooks: designed for deterministic updates, you control what and when to update. Perfect for maintaining steady FPS.
- ** MobX:** optimized for UI dependency graphs — excellent for data-driven interfaces, but overkill for frame-by-frame logic.
🏆 Best fit for games: Phaser Hooks 🏆 Best fit for UIs: MobX
Section titled “🏆 Best fit for games: Phaser Hooks 🏆 Best fit for UIs: MobX”🧮 Computed Values
Section titled “🧮 Computed Values”phaser-hooks:
const player = withPlayer(this);const healthPercent = withComputedState(  this,  'healthPercent',  player,  (p) => (p.hp / p.maxHp) * 100);
healthPercent.get(); // 100✅ Pros:
- Explicit and clear logic
- Full control over recalculation
⚠️ Considerations:
- More boilerplate
MobX:
class PlayerStore {  hp = 100;  maxHp = 100;
  get healthPercent() {    return (this.hp / this.maxHp) * 100;  }}✅ Pros:
- Auto memoization
- Elegant syntax
⚠️ Considerations:
- Implicit dependency tracking can confuse beginners
🧰 TypeScript Support
Section titled “🧰 TypeScript Support”Both libraries offer strong typing and inference, ensuring safe and predictable updates.
phaser-hooks:
type Player = { hp: number; level: number };const player = withLocalState<Player>(this, 'player', { hp: 100, level: 1 });
player.get().hp; // TypeScript knows: numberplayer.patch({ hp: 50 }); // ✅ Type-safeplayer.patch({ invalid: true }); // ❌ Type errorMobX:
class PlayerStore {  hp: number = 100;  level: number = 1;
  takeDamage(amount: number) {    this.hp -= amount;  }}
playerStore.hp; // TypeScript knows: numberplayerStore.takeDamage(10); // ✅ Type-safeplayerStore.takeDamage('invalid'); // ❌ Type error📦 Bundle Size
Section titled “📦 Bundle Size”phaser-hooks: ~10KB gzipped (minimal) MobX: ~63KB gzipped (core only)
If you care about initial load times or embedding games in iframes, Phaser Hooks wins easily.
Section titled “If you care about initial load times or embedding games in iframes, Phaser Hooks wins easily.”📚 Learning Curve
Section titled “📚 Learning Curve”phaser-hooks:
// Easy to learn - 4 core concepts:state.get()    // Readstate.set()    // Writestate.on()     // Subscribeunsubscribe()  // Clean upTime to productivity: ~30 minutes
MobX:
// More concepts to learn:makeAutoObservable() // Observables@computed            // Computed values@action              // Actionsautorun()            // Reactionsreaction()           // Custom reactionsrunInAction()        // TransactionsLearn observables, actions, computed, autorun, reactions, decorators… Time to productivity: ~2-3 hours
🎯 When to Choose phaser-hooks
Section titled “🎯 When to Choose phaser-hooks”✅ Choose phaser-hooks if:
- You’re building a Phaser-only game
- You prefer explicit, predictable updates over automatic reactivity
- You value small bundles and fast loading
- You want quick onboarding for your team
- You need tight Phaser integration (automatic cleanup, etc.)
- You like functional programming patterns (hooks, not classes)
Phaser Hooks is not trying to be Mobx, it’s a precision tool built specifically for game developers.
Section titled “Phaser Hooks is not trying to be Mobx, it’s a precision tool built specifically for game developers.”When to Choose MobX
Section titled “When to Choose MobX”✅ Choose MobX if:
- You need automatic reactivity for complex UIs
- You’re already using MobX in other parts of your app
- You want to share state between Phaser and React/Vue
- You’re building a large, complex game with deep reactive dependencies
🔗 Can You Use Both?
Section titled “🔗 Can You Use Both?”Yes! They’re not mutually exclusive, you can use both in the same project:
// Use phaser-hooks for Phaser-specific stateconst player = withPlayer(this);
// Use MobX for complex UI stateimport { uiStore } from './stores/UIStore';
autorun(() => {  // React to MobX store  if (uiStore.showInventory) {    this.showInventoryUI();  }});🔄 Migration Path Examples
Section titled “🔄 Migration Path Examples”From MobX to Phaser Hooks
Section titled “From MobX to Phaser Hooks”// Before (MobX)class PlayerStore {  hp = 100;  takeDamage(amount: number) { this.hp -= amount; }}
// After (phaser-hooks)export function withPlayer(scene: Phaser.Scene) {  const state = withLocalState(scene, 'player', { hp: 100 });  return {    ...state,    takeDamage: (amount: number) => {      state.patch({ hp: state.get().hp - amount });    }  };}Summary
Section titled “Summary”| Criteria | Winner | 
|---|---|
| Simplicity | ✅ Phaser Hooks | 
| Bundle Size | ✅ Phaser Hooks | 
| Phaser Integration | ✅ Phaser Hooks | 
| Scene Lifecycle Integration | ✅ Phaser Hooks | 
| Computed Values | ✅ MobX | 
| Automatic Reactivity | ✅ MobX | 
| Framework Agnostic | ✅ Mobx | 
| Learning Curve | ✅ Phaser Hooks | 
| Mature Ecosystem | ✅ MobX | 
🧠 Final Thoughts
Section titled “🧠 Final Thoughts”MobX is a fantastic general-purpose reactivity engine. But Phaser Hooks is crafted for one purpose — to make state management inside Phaser games simple, stable, and fast.
If your focus is on performance, clarity, and game-specific integration, Phaser Hooks gives you the precision you need — without the overhead of a UI framework.