CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-pixi--react

React integration for PixiJS enabling declarative 2D graphics programming with JSX and React patterns.

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

hooks.mddocs/

React Hooks

@pixi/react provides React hooks for accessing PixiJS application state, integrating with the ticker system, and dynamically extending components. These hooks follow React patterns and integrate seamlessly with component lifecycle.

Capabilities

useApplication Hook

Retrieves the nearest PixiJS Application from the React context.

/**
 * Retrieves the nearest Pixi.js Application from the Pixi React context
 * @returns ApplicationState containing the app instance and initialization status
 * @throws Error if used outside of Application component context
 */
function useApplication(): ApplicationState;

interface ApplicationState {
  /** The PixiJS Application instance */
  app: PixiApplication;
  /** Whether the application has finished initialization */
  isInitialised: boolean;
  /** Whether the application is currently initializing */
  isInitialising: boolean;
}

Usage Examples:

import { useApplication } from "@pixi/react";
import { useEffect, useState } from "react";

const AppInfoComponent = () => {
  const { app, isInitialised } = useApplication();
  const [fps, setFps] = useState(0);

  useEffect(() => {
    if (!isInitialised) return;

    // Access application properties
    console.log("Canvas size:", app.canvas.width, app.canvas.height);
    console.log("Renderer type:", app.renderer.type);

    // Monitor FPS
    const updateFPS = () => setFps(app.ticker.FPS);
    app.ticker.add(updateFPS);

    return () => app.ticker.remove(updateFPS);
  }, [app, isInitialised]);

  if (!isInitialised) {
    return <pixiText text="Loading..." />;
  }

  return (
    <pixiText 
      text={`FPS: ${fps.toFixed(1)}`} 
      x={10} 
      y={10} 
      style={{ fontSize: 14, fill: "white" }}
    />
  );
};

// Access renderer information
const RendererInfo = () => {
  const { app, isInitialised } = useApplication();

  if (!isInitialised) return null;

  const rendererInfo = {
    type: app.renderer.type,
    resolution: app.renderer.resolution,
    backgroundColor: app.renderer.background.color,
  };

  return (
    <pixiText 
      text={`Renderer: ${rendererInfo.type}`}
      x={10}
      y={30}
    />
  );
};

useTick Hook

Attaches a callback to the application's Ticker for frame-based animations and updates.

/**
 * Attaches a callback to the application's Ticker
 * @param options - Either a callback function or options object with callback
 */
function useTick<T>(options: TickerCallback<T> | UseTickOptions<T>): void;

type TickerCallback<T> = (ticker: Ticker, context?: T) => void;

interface UseTickOptions<T> {
  /** The function to be called on each tick */
  callback: TickerCallback<T>;
  /** The value of `this` within the callback */
  context?: T;
  /** Whether this callback is currently enabled */
  isEnabled?: boolean;
  /** The priority of this callback compared to other callbacks on the ticker */
  priority?: number;
}

Usage Examples:

import { useTick } from "@pixi/react";
import { useState, useCallback } from "react";

// Simple animation with function callback
const RotatingSprite = ({ texture }) => {
  const [rotation, setRotation] = useState(0);

  useTick(useCallback((ticker) => {
    setRotation(prev => prev + 0.01 * ticker.deltaTime);
  }, []));

  return (
    <pixiSprite 
      texture={texture} 
      rotation={rotation}
      anchor={0.5}
      x={400}
      y={300}
    />
  );
};

// Advanced animation with options
const AnimatedGraphics = () => {
  const [scale, setScale] = useState(1);
  const [isAnimating, setIsAnimating] = useState(true);

  useTick({
    callback: useCallback((ticker) => {
      setScale(prev => {
        const newScale = prev + Math.sin(ticker.lastTime * 0.01) * 0.001;
        return Math.max(0.5, Math.min(2, newScale));
      });
    }, []),
    isEnabled: isAnimating,
    priority: 1, // Higher priority than default (0)
  });

  const drawCallback = useCallback((graphics) => {
    graphics.clear();
    graphics.setFillStyle({ color: "blue" });
    graphics.circle(0, 0, 50);
    graphics.fill();
  }, []);

  return (
    <pixiContainer>
      <pixiGraphics 
        draw={drawCallback}
        scale={scale}
        anchor={0.5}
        x={200}
        y={200}
      />
      <pixiText 
        text={isAnimating ? "Click to pause" : "Click to resume"}
        interactive
        onPointerTap={() => setIsAnimating(!isAnimating)}
        x={10}
        y={10}
      />
    </pixiContainer>
  );
};

// Context-based animation
class AnimationContext {
  constructor(public speed: number = 1) {}
  
  updateRotation(rotation: number, ticker: Ticker): number {
    return rotation + 0.02 * this.speed * ticker.deltaTime;
  }
}

const ContextAnimation = () => {
  const [rotation, setRotation] = useState(0);
  const [context] = useState(() => new AnimationContext(2));

  useTick({
    callback: function(ticker) {
      // `this` refers to the context object
      setRotation(prev => this.updateRotation(prev, ticker));
    },
    context,
  });

  return (
    <pixiSprite 
      texture={texture} 
      rotation={rotation}
      anchor={0.5}
    />
  );
};

useExtend Hook

Hook version of the extend function for dynamically exposing PixiJS components.

/**
 * Hook version of extend function for exposing Pixi.js components
 * @param objects - Object mapping component names to PixiJS constructor classes
 */
function useExtend(objects: Parameters<typeof extend>[0]): void;

Usage Examples:

import { useExtend } from "@pixi/react";
import { useMemo, useState, useEffect } from "react";

// Dynamic component loading
const DynamicComponentLoader = ({ enableAdvanced }) => {
  const [advancedComponents, setAdvancedComponents] = useState(null);

  useEffect(() => {
    if (enableAdvanced) {
      import("pixi.js").then(({ NineSlicePlane, SimplePlane, TilingSprite }) => {
        setAdvancedComponents({ NineSlicePlane, SimplePlane, TilingSprite });
      });
    }
  }, [enableAdvanced]);

  // Extend components when they become available
  useExtend(advancedComponents || {});

  if (!enableAdvanced || !advancedComponents) {
    return <pixiText text="Basic mode" />;
  }

  return (
    <pixiContainer>
      <pixiTilingSprite 
        texture={texture}
        width={200}
        height={200}
      />
      <pixiNineSlicePlane 
        texture={buttonTexture}
        leftWidth={10}
        rightWidth={10}
        topHeight={10}
        bottomHeight={10}
      />
    </pixiContainer>
  );
};

// Conditional extension based on props
const ConditionalExtender = ({ includeFilters, includeMesh }) => {
  const components = useMemo(() => {
    const result = {};
    
    if (includeFilters) {
      import("pixi.js").then(({ BlurFilter, ColorMatrixFilter }) => {
        Object.assign(result, { BlurFilter, ColorMatrixFilter });
      });
    }
    
    if (includeMesh) {
      import("pixi.js").then(({ Mesh, PlaneGeometry }) => {
        Object.assign(result, { Mesh, PlaneGeometry });
      });
    }
    
    return result;
  }, [includeFilters, includeMesh]);

  useExtend(components);

  return (
    <pixiContainer>
      {includeFilters && (
        <pixiText text="Filters available" />
      )}
      {includeMesh && (
        <pixiText text="Mesh components available" y={20} />
      )}
    </pixiContainer>
  );
};

// Plugin integration with hooks
const PluginIntegration = () => {
  const [pluginComponents, setPluginComponents] = useState({});

  useEffect(() => {
    // Dynamically load plugin components
    Promise.all([
      import("pixi-spine").then(m => ({ Spine: m.Spine })),
      import("pixi-viewport").then(m => ({ Viewport: m.Viewport })),
    ]).then(([spine, viewport]) => {
      setPluginComponents({ ...spine, ...viewport });
    });
  }, []);

  useExtend(pluginComponents);

  const hasSpine = 'Spine' in pluginComponents;
  const hasViewport = 'Viewport' in pluginComponents;

  return (
    <pixiContainer>
      {hasViewport && hasSpine && (
        <pixiViewport 
          screenWidth={800}
          screenHeight={600}
          worldWidth={1200}
          worldHeight={900}
        >
          <pixiSpine spineData={spineData} />
        </pixiViewport>
      )}
    </pixiContainer>
  );
};

Hook Rules and Best Practices

Dependency Management

// ✓ Stable callback reference
const RotatingSprite = () => {
  const [rotation, setRotation] = useState(0);
  
  const tickCallback = useCallback((ticker) => {
    setRotation(prev => prev + 0.01 * ticker.deltaTime);
  }, []); // Empty dependencies - callback is stable
  
  useTick(tickCallback);
  
  return <pixiSprite rotation={rotation} />;
};

// ✗ Avoid recreating callback on every render
const BadExample = () => {
  const [rotation, setRotation] = useState(0);
  
  // This creates a new function on every render!
  useTick((ticker) => {
    setRotation(prev => prev + 0.01 * ticker.deltaTime);
  });
  
  return <pixiSprite rotation={rotation} />;
};

Conditional Hook Usage

// ✓ Use isEnabled option for conditional ticking
const ConditionalAnimation = ({ shouldAnimate }) => {
  useTick({
    callback: useCallback((ticker) => {
      // Animation logic
    }, []),
    isEnabled: shouldAnimate, // Control via options
  });
  
  return <pixiSprite />;
};

// ✗ Don't conditionally call hooks
const BadConditional = ({ shouldAnimate }) => {
  if (shouldAnimate) {
    useTick(() => {}); // Violates rules of hooks!
  }
  
  return <pixiSprite />;
};

Error Handling

The hooks include comprehensive error handling:

  • useApplication: Throws meaningful error if used outside Application context
  • useTick: Validates callback function and handles ticker lifecycle
  • useExtend: Validates component objects and handles registration failures
  • All hooks properly clean up resources during component unmounting

Install with Tessl CLI

npx tessl i tessl/npm-pixi--react

docs

application.md

component-extension.md

hooks.md

index.md

jsx-elements.md

root-creation.md

tile.json