React integration for PixiJS enabling declarative 2D graphics programming with JSX and React patterns.
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
@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.
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}
/>
);
};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}
/>
);
};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>
);
};// ✓ 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} />;
};// ✓ 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 />;
};The hooks include comprehensive error handling:
Install with Tessl CLI
npx tessl i tessl/npm-pixi--react