npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@realiz3r/react-phaser

v0.1.3

Published

A custom React-style Phaser renderer, providing a declarative way to build scenes with VNodes and hooks.

Readme

@realiz3r/react-phaser

npm version license bundle size

A lightweight, high-performance, React-style renderer for Phaser 3. Build your game worlds declaratively using components, hooks, and object pooling.


🌟 Why React Phaser?

Managing complex state and game object hierarchies in Phaser usually leads to imperative "spaghetti code." This library provides a declarative abstraction layer without sacrificing Phaser's performance.

  • Declarative Composition: Use functional components to describe your scene graph.
  • Hook-based Logic: First-class support for useState, useEffect, and native Phaser lifecycles via useUpdate.
  • Lightweight State Management: Built-in Pinia-style reactive stores with auto-logging.
  • Optimized Pooling: First-class support for Phaser Arcade Physics pooling.

🚀 Installation

npm install @realiz3r/react-phaser

[!NOTE] This library requires Phaser 3.80+ and targets ES2020+.

📖 Quick Start

Demo project: SpaceShooterGame

import { mountRoot, createNode, useUpdate, useRef } from '@realiz3r/react-phaser';

function Player({ x, y }: { x: number, y: number }) {
  const spriteRef = useRef<Phaser.Physics.Arcade.Sprite>(null);

  useUpdate(() => {
    if (spriteRef.current) spriteRef.current.x += 1;
  });

  return createNode('physics-sprite', {
    ref: spriteRef,
    x, y,
    texture: 'ship',
    velocityX: 100
  });
}

class GameScene extends Phaser.Scene {
  create() {
    // Mount a VNode (React-style):
    mountRoot(this, createNode(Player, { x: 100, y: 100 }));
    // Or mount a root component + props:
    // mountRoot(this, Player, { x: 100, y: 100 });
  }
}

🛠 Features & API

VNode Primitives

| Type | Description | Key Props | | :--- | :--- | :--- | | container | Groups children into a Phaser.GameObjects.Container. | x, y, width/height, input handlers | | text | Renders a Phaser.GameObjects.Text. | text, fontSize, color, wordWrapWidth | | sprite / image| Renders standard textures. | texture, frame, tint, flipX/flipY | | rect / graphics| Renders graphics-backed shapes. | fill, lineColor, strokeWidth | | physics-sprite | Arcade Physics enabled sprite. | velocityX/Y, bounce, drag, gravityY | | physics-group | Managed Arcade Group for pooling. | config (Phaser Group Config) | | fragment | Transparent wrapper for multiple children. | (None) |

Interactivity & Input

You can attach input handlers to most nodes: onClick, onPointerDown, onPointerUp, onPointerMove, onPointerOver, onPointerOut, onWheel, onDragStart, onDrag, onDragEnd, onDragEnter, onDragOver, onDragLeave, onDrop.

  • Auto-interactive: if you provide any input handler (or draggable / dropZone), the node becomes interactive without needing interactive: true.
  • Auto-draggable: if you provide any drag handler, draggable defaults to true (override with an explicit draggable: false).
  • Hit areas: container and rect/graphics need a hit area. Provide width/height (or w/h) or pass hitArea/hitAreaCallback.
  • Extras: useHandCursor, cursor, pixelPerfect, alphaTolerance, draggable, dropZone.

Hooks Reference

| Hook | Description | | :--- | :--- | | useState<T>(initial) | Standard state management. | | useUpdate(callback) | Runs every frame on Phaser's update event. | | useStore(hook, selector?)| Subscribes to a game-state store. | | useRef<T>(initial) | Persistent reference to Phaser GameObjects. | | useMemo / useCallback | Performance optimization for values and functions. | | useEvent(handler) | Stable identity for event callbacks. | | useScene() | Access the current Phaser.Scene. | | useLayoutEffect(cb, deps)| Runs after Phaser objects commit (before useEffect). | | useEffect(cb, deps) | Runs after commit (after useLayoutEffect). | | onMount(cb) | Utility for one-time initialization logic. |


🧭 Frame Lifecycle & Syncing (Read This)

React Phaser intentionally separates game loop logic (Phaser's update) from render/commit (reconciliation that creates/updates/destroys Phaser objects).

Frame Lifecycle (High-level)

  1. Phaser emits update → your useUpdate(...) callbacks run.
  2. If you call setState(...) / mutate a store, a render is queued (microtask).
  3. After the current tick finishes, reconciliation runs and commits Phaser object updates.
  4. useLayoutEffect(...) runs right after commit, then useEffect(...).

This means state updates do not immediately change the Phaser world inside the same useUpdate callback.

Common Pitfall: "Write State" then "Read Phaser World" in the Same Tick

[!WARNING] Don’t spawn via setState(...) and then “cleanup/sync” by reading group.children in the same useUpdate pass. The group still reflects the previous committed render, so your sync logic can accidentally delete the just-spawned items (e.g. projectile counts oscillate).

// ❌ Buggy pattern (state write + Phaser read for sync in the same tick)
useUpdate(() => {
  setProjectiles((prev) => [...prev, spawnProjectile()]);

  // Still reflects the previous commit (the new projectile isn't in the group yet)
  const idsInWorld = new Set(projectilesGroup.children.entries.map((c) => c.getData("id")));
  setProjectiles((prev) => prev.filter((p) => idsInWorld.has(p.id)));
});

Avoid these patterns:

  • Calling setProjectiles(...) and then immediately filtering state based on projectilesGroup.children.
  • Treating Phaser objects as the source of truth and trying to “sync state to match them” every frame.

Prefer one of these approaches:

  • State is the source of truth: compute the entire next state in a single setState(prev => next) without reading Phaser world.
  • Order your pass: do any Phaser-world-derived cleanup first, then spawn (still in one tick), or combine both into one state update.
  • Read-after-commit: if you must observe the committed Phaser world, do it in useLayoutEffect / useEffect that runs after reconciliation.
// ✅ Compute next state in one pass (no Phaser-world sync needed)
useUpdate(() => {
  setProjectiles((prev) => {
    const cleaned = prev.filter((p) => !p.dead);
    return [...cleaned, spawnProjectile()];
  });
});

📦 State Management (game-state)

The library includes a lightweight, Pinia-inspired state management system optimized for game development. Mutations are batched per action and recorded by key/path (e.g. score, player.hp).

Defining a Store

import { defineGameStore } from '@realiz3r/react-phaser';

export const useGameStore = defineGameStore('game', {
  state: () => ({
    score: 0,
    health: 100
  }),
  getters: {
    isGameOver: (state) => state.health <= 0
  },
  actions: {
    addScore(points: number) {
      this.$state.score += points;
    }
  }
});

Using in Components

function ScoreDisplay() {
  const score = useStore(useGameStore, s => s.score);
  return createNode('text', { text: `Score: ${score}`, x: 10, y: 10 });
}

Watching State

You can watch store-derived values outside React-style rendering:

const store = useGameStore();
store.$watch(s => s.player, (next, prev, mutation) => {
  console.log(mutation.action, mutation.changes);
});

🚀 Optimized Pooling

Using physics-group allows you to manage hundreds of objects with minimal GC pressure.

function Bullet({ x, y }: { x: number, y: number }) {
  return createNode('physics-sprite', { x, y, texture: 'bullet' });
}

function BulletSpawner() {
  const bullets = useStore(useBulletStore);

  return createNode('physics-group', {
    config: { 
      classType: Phaser.Physics.Arcade.Sprite, 
      maxSize: 100, 
      defaultKey: null 
    }
  },
    ...bullets.map(b => createNode(Bullet, {
      key: b.id,
      ...b
    }))
  );
}

🔍 Debugging & Tooling

The library provides built-in tools for state inspection and travel.

Configuration

You can customize the behavior of the state manager:

import { GameState } from '@realiz3r/react-phaser';

GameState.config({
  loggingEnabled: true,         // Set to false to disable action logging
  maxLogSize: 500,              // Customize the number of mutations to keep in memory (default: 1000)
  cloneSnapshots: true,         // Return stable snapshots (deep-cloned)
  cloneMutations: true,         // Store stable log entries (deep-cloned)
  nonActionMutation: 'warn'     // 'ignore' | 'warn' | 'throw'
});

State Snapshotting

Access the global state and logs via the GameState manager:

import { GameState } from '@realiz3r/react-phaser';

// Get a snapshot of all registered stores
const currentWorldState = GameState.snapshot();

// Export state and action logs for bug reporting
console.log(GameState.dump());

🤝 Contributing

We welcome contributions! Please feel free to submit a Pull Request.

📄 License

MIT © kllilizxc