Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/sanchedev/fraxel/llms.txt

Use this file to discover all available pages before exploring further.

All core hooks are imported from fraxel/hooks and called inside component functions. They form the foundation of Fraxel’s reactivity and lifecycle system — useSignal and useComputed provide reactive state, useEffect and useMount manage lifecycle, and the remaining hooks wire together nodes, events, game controls, and cross-component communication. Every hook must be called at the top level of a component function, never inside conditionals or loops.

Quick Reference

HookSignatureDescription
useSignaluseSignal<T>(initialValue: T): [SignalGetter<T>, SignalSetter<T>]Creates a reactive signal with an initial value
useComputeduseComputed<T>(fn: () => T): SignalGetter<T>Creates a derived signal that recomputes when dependencies change
useEffectuseEffect(fn: () => void | (() => void)): voidRuns an effect on mount and whenever tracked signals change
useNodeuseNode<T extends PrimaryNode>(type: T): NodeReference<T>Creates a typed reference to pass as a JSX ref prop
useEventuseEvent<N, K>(node: NodeReference<N>, event: K, listener): voidSubscribes to a node event with automatic cleanup on destroy
useMountuseMount(fn: () => void | (() => void)): voidRuns once on mount; returned function is called on destroy
useSpawnuseSpawn<T>(node: NodeReference<T>): (jsx: Fraxel.Node) => voidReturns a function to dynamically add children to a node
useGameuseGame(): GameControlsReturns game-level controls for play, pause, and scene management
createContextcreateContext<T>(defaultValue: T): ContextCreated<T>Creates a context with a default value and a Provider component
useContextuseContext<T>(context: ContextCreated<T>): TRetrieves the current value of a context
createTriggercreateTrigger<T extends any[]>(): Trigger<T>Creates a Trigger instance for pub/sub communication
useTriggeruseTrigger<T>(trigger: Trigger<T>, fn: Fun<T>): voidSubscribes to a Trigger with auto-cleanup on destroy
useRefuseRef<T>(value: T): Reference<T>Creates a mutable reference that persists across renders
useChilduseChild<T>(path: (string | symbol)[], type: T): NodeReference<T>Retrieves a child node reference by path
useScriptuseScript<T extends FraxelScript<PrimaryNode>>(node: NodeReference<PrimaryNode>): SignalGetter<T | undefined>Retrieves the script instance attached to a node

useSignal

useSignal<T>(initialValue: T): [SignalGetter<T>, SignalSetter<T>]
Creates a reactive signal. Returns a tuple of a getter function and a setter function. Calling the getter inside a useEffect or useComputed automatically registers it as a dependency — the effect or computed will re-run whenever the signal changes. When the owning node is destroyed, all subscribers are cleaned up automatically via clearSubs.
initialValue
T
required
The initial value for the signal.
Returns:
[0] getter
SignalGetter<T>
A callable function that returns the current signal value. Calling getter() tracks it as a reactive dependency. Call getter.value() to read the value without tracking.
[1] setter
SignalSetter<T>
A function that updates the signal value and notifies all subscribers. Equivalent to setting signal.value = newValue.
import { useSignal, useEffect } from 'fraxel/hooks'

function HealthBar() {
  const [health, setHealth] = useSignal(100)

  useEffect(() => {
    console.log('Health changed:', health())
  })

  const handleDamage = () => setHealth(health() - 10)

  return (
    <transform>
      <rectangle size={[health(), 10]} fillColor={[1, 0, 0, 1]} />
      <clickable size={[32, 32]} onClick={handleDamage} />
    </transform>
  )
}

useComputed

useComputed<T>(fn: () => T): SignalGetter<T>
Creates a derived signal whose value is computed from other signals. The function fn is called immediately and re-evaluated automatically whenever any signal accessed inside it changes. Like useSignal, the computed signal cleans up its own subscriptions when the node is destroyed.
fn
() => T
required
The computation function. Any SignalGetter called inside this function is automatically tracked as a dependency.
Returns:
getter
SignalGetter<T>
A callable getter that returns the current computed value and can itself be tracked as a dependency by other computed values or effects.
import { useSignal, useComputed, useEffect } from 'fraxel/hooks'

function CooldownBar() {
  const [elapsed, setElapsed] = useSignal(0)

  // Automatically recomputes whenever `elapsed` changes
  const progress = useComputed(() => elapsed() / 3) // 3-second cooldown

  useEffect(() => {
    console.log('Progress:', progress()) // 0 → 1 over 3 s
  })

  return (
    <transform>
      <rectangle size={[100 * progress(), 10]} fillColor={[0, 1, 0, 1]} />
    </transform>
  )
}

useEffect

useEffect(fn: () => void | (() => void)): void
Runs fn when the node starts and again whenever any reactive signal accessed inside fn changes. Re-executions are batched — if multiple signals change synchronously in the same block, the effect runs only once before the next frame, deferred via queueMicrotask. If fn returns a cleanup function, that function is called when the node is destroyed.
fn
() => void | (() => void)
required
The effect function. Any SignalGetter called inside it is automatically tracked. May return a cleanup function that runs when the node is destroyed.
import { useSignal, useEffect } from 'fraxel/hooks'

function Tracker() {
  const [x, setX] = useSignal(0)
  const [y, setY] = useSignal(0)
  const [z, setZ] = useSignal(0)

  // All three synchronous changes trigger a single effect run
  useEffect(() => {
    console.log('Position:', x(), y(), z())
  })

  const teleport = () => {
    setX(10)
    setY(20)
    setZ(30)
    // → effect runs once (not three times), via queueMicrotask
  }

  return <clickable size={[64, 64]} onClick={teleport} />
}
useEffect differs from useMount in that it re-runs when tracked signals change. Use useMount when you only need to run once on mount with no reactive dependencies.

useNode

useNode<T extends PrimaryNode>(type: T): NodeReference<T>
Creates a typed NodeReference for the given node type. Pass the returned reference to the ref prop on the corresponding JSX element. The reference is populated when the node mounts and throws a NodeNotInitializedError if accessed before mounting.
type
PrimaryNode
required
The node type to reference. Must be a member of the PrimaryNode enum.
Returns:
NodeReference<T>
object
A reference object with a node property (the live node instance) and a signal getter (SignalGetter<NodeInstances[T] | null>) that tracks when the node becomes available.
PrimaryNode enum values:
ValueJSX tagDescription
PrimaryNode.Sprite<sprite>Displays a texture
PrimaryNode.Transform<transform>Positions and organizes children
PrimaryNode.Group<group>Groups children without position management
PrimaryNode.Collider<collider>Detects collisions
PrimaryNode.RayCast<ray-cast>Projects a ray to detect colliders
PrimaryNode.Clickable<clickable>Detects pointer events
PrimaryNode.Rectangle<rectangle>Renders a filled/stroked rectangle
PrimaryNode.Timer<timer>Time-based utility node
PrimaryNode.Text<text>Renders text on the canvas
PrimaryNode.AudioPlayer<audio-player>Plays audio buffers
PrimaryNode.Camera<camera>Controls the viewport
PrimaryNode.RigidBody<rigid-body>Adds physics simulation
PrimaryNode.AnimationPlayer<animation-player>Plays frame-based animations
import { useNode, useEvent } from 'fraxel/hooks'
import { PrimaryNode, shapes } from 'fraxel'

function Enemy() {
  const collider = useNode(PrimaryNode.Collider)

  useEvent(collider, 'colliderEntered', (other) => {
    console.log('Hit by:', other.node.tag)
  })

  return (
    <transform>
      <sprite textureId={ENEMY_TEXTURE} />
      <collider
        ref={collider}
        shape={shapes.circle(16)}
        group={['enemy']}
        collidesWith={['projectile']}
      />
    </transform>
  )
}
See also: useChild, /api/nodes-2d, /api/nodes-utility

useEvent

useEvent<N extends PrimaryNode, K extends keyof Events<N>>(
  node: NodeReference<N>,
  eventName: K,
  listener: Events<N>[K],
): void
Subscribes to a named event on a NodeReference. The subscription is type-safe — TypeScript will only allow event names and listener signatures that are valid for the given node type. The listener is connected when the node starts and is automatically removed when the node is destroyed, preventing memory leaks.
node
NodeReference<N>
required
The node reference to subscribe to.
eventName
K
required
The name of the event. TypeScript enforces valid event names for the node type.
listener
Events<N>[K]
required
The callback function. Its parameter types are automatically inferred from the event.
Common events by node type:
EventNode(s)Payload
startedAll nodes()
destroyedAll nodes()
updatedAll nodes(delta: number)
colliderEnteredCollider, RayCast(other: Collider)
colliderExitedCollider, RayCast(other: Collider)
clickedClickable()
mouseEnteredClickable()
mouseExitedClickable()
mouseOverClickable(position: Vector2)
timeoutTimer()
timeChangedTimer(time: number)
animationEndedAnimationPlayer()
animationChangedAnimationPlayer(name: string)
import { useNode, useEvent } from 'fraxel/hooks'
import { PrimaryNode } from 'fraxel'

function Turret() {
  const raycast = useNode(PrimaryNode.RayCast)

  useEvent(raycast, 'colliderEntered', (other) => {
    console.log('Target acquired:', other.node.name)
  })

  useEvent(raycast, 'colliderExited', () => {
    console.log('Target lost')
  })

  return (
    <ray-cast
      ref={raycast}
      direction={[200, 0]}
      collidesWith={['enemy']}
    />
  )
}

useMount

useMount(fn: () => void | (() => void)): void
Runs fn exactly once when the node starts. Unlike useEffect, it does not track signal dependencies and will never re-run. If fn returns a cleanup function, that cleanup is called automatically when the node is destroyed.
fn
() => void | (() => void)
required
The mount function. May return a cleanup function that runs when the node is destroyed.
import { useMount } from 'fraxel/hooks'

function MusicZone() {
  useMount(() => {
    const audio = new AudioContext()
    audio.resume()

    console.log('Zone activated')

    // Cleanup runs when node is destroyed
    return () => {
      audio.close()
      console.log('Zone deactivated')
    }
  })

  return <transform />
}
Use useMount for side effects that depend only on external resources (audio contexts, subscriptions, timers) and have no reactive signal dependencies. For effects that should re-run when signals change, use useEffect instead.

useSpawn

useSpawn<T extends PrimaryNode>(node: NodeReference<T>): (jsx: Fraxel.Node) => void
Returns a function that appends JSX children to the referenced node at runtime. The spawned nodes are rendered using the same context (including any active providers) as the calling component. Call the returned spawn function from event handlers or effects — not during the component’s initial render.
node
NodeReference<T>
required
The node reference whose underlying node will become the parent of spawned children.
Returns:
spawn
(jsx: Fraxel.Node) => void
A function that accepts JSX and appends the rendered nodes as children of the target node.
import { useNode, useSpawn } from 'fraxel/hooks'
import { PrimaryNode } from 'fraxel'

function BulletSpawner() {
  const container = useNode(PrimaryNode.Transform)
  const spawn = useSpawn(container)

  const fireBullet = () => {
    spawn(
      <transform position={[0, 0]}>
        <sprite textureId={BULLET_TEXTURE} />
        <collider shape={shapes.circle(4)} group={['projectile']} />
      </transform>
    )
  }

  return (
    <transform ref={container}>
      <clickable size={[64, 32]} onClick={fireBullet} />
    </transform>
  )
}
See also: useNode, /guide/hooks

useGame

useGame(): GameControls
Returns a GameControls object with methods to control the game loop and manage scenes. Safe to call from any component — useGame reads from the global Game singleton. Returns:
play
() => void
Starts or resumes the game loop.
pause
() => void
Pauses the game loop.
changeScene
(name: string) => Promise<void>
Loads and switches to the named scene. The scene must be registered in the <Game> component.
preloadScene
(name: string) => Promise<() => void>
Preloads a scene while the game is running. Returns a function to activate the preloaded scene when ready.
getSize
() => Vector2
Returns the game canvas dimensions as a Vector2.
import { useGame, useNode, useEvent } from 'fraxel/hooks'
import { PrimaryNode } from 'fraxel'

function MainMenu() {
  const game = useGame()
  const playBtn = useNode(PrimaryNode.Clickable)

  useEvent(playBtn, 'clicked', async () => {
    await game.changeScene('gameplay')
  })

  return (
    <transform>
      <sprite textureId={MENU_BG} />
      <clickable ref={playBtn} size={[120, 40]} />
    </transform>
  )
}

createContext / useContext

createContext<T>(defaultValue: T): ContextCreated<T>
useContext<T>(context: ContextCreated<T>): T
createContext creates a context object with a default value and a Provider component. Wrap a subtree with <Context.Provider value={...}> to supply a value to all descendants. useContext retrieves the nearest ancestor provider’s value, or the default if no provider is present.

createContext

defaultValue
T
required
The value returned by useContext when no matching Provider is found in the ancestor chain.
Returns:
ContextCreated<T>
object
An object with a Provider JSX component (props: { value: T, children }) and the stored defaultValue.

useContext

context
ContextCreated<T>
required
The context object returned by createContext.
Returns:
T
T
The value provided by the nearest ancestor Provider, or defaultValue if none exists.
import { createContext, useContext } from 'fraxel/hooks'

// Define the context (typically in a shared module)
interface GameState {
  score: number
  lives: number
}

const GameCtx = createContext<GameState>({ score: 0, lives: 3 })

// Provider — wraps the subtree that should have access
function GameScene() {
  return (
    <GameCtx.Provider value={{ score: 100, lives: 3 }}>
      <HUD />
      <Player />
    </GameCtx.Provider>
  )
}

// Consumer — any descendant component
function HUD() {
  const { score, lives } = useContext(GameCtx)

  return (
    <transform>
      <text text={`Score: ${score}`} position={[10, 10]} />
      <text text={`Lives: ${lives}`} position={[10, 30]} />
    </transform>
  )
}
See also: /guide/hooks

createTrigger / useTrigger

createTrigger<T extends any[]>(): Trigger<T>
useTrigger<T extends any[]>(trigger: Trigger<T>, fn: Fun<T>): void
createTrigger creates a typed Trigger instance that acts as a pub/sub channel. useTrigger subscribes a component to that trigger with automatic cleanup when the node is destroyed. Call trigger.emit(...args) from anywhere to invoke all connected listeners.

createTrigger

Returns:
Trigger<T>
object
A Trigger instance with connect, disconnect, and emit methods.

useTrigger

trigger
Trigger<T>
required
The Trigger to subscribe to.
fn
Fun<T>
required
The callback to invoke when the trigger emits. Automatically disconnected when the node is destroyed.

Trigger class

trigger.emit(...args)
void
Invokes all connected listeners with the provided arguments.
trigger.connect(fn)
void
Manually adds a listener. Prefer useTrigger inside components for automatic cleanup.
trigger.disconnect(fn)
void
Removes a previously connected listener.
import { createTrigger, useTrigger, useNode, useEvent } from 'fraxel/hooks'
import { PrimaryNode } from 'fraxel'

// Shared module — define trigger outside any component
const onEnemyKilled = createTrigger<[enemyId: string]>()

// Listener component — subscribes with auto-cleanup
function ScoreTracker() {
  useTrigger(onEnemyKilled, (enemyId) => {
    console.log('Enemy killed:', enemyId)
    // update score signal here
  })

  return <transform />
}

// Emitter component — fires trigger on collider event
function Enemy({ id }: { id: string }) {
  const collider = useNode(PrimaryNode.Collider)

  useEvent(collider, 'colliderEntered', () => {
    onEnemyKilled.emit(id)
  })

  return (
    <transform>
      <collider ref={collider} shape={shapes.circle(12)} group={['enemy']} />
    </transform>
  )
}

useRef

useRef<T>(value: T): Reference<T>
Creates a Reference object with a mutable current property that persists across re-renders. Unlike useSignal, mutating ref.current does not trigger any reactive updates — it is a plain mutable container. Use it to store imperative values like frame counters, accumulated deltas, or external resource handles.
value
T
required
The initial value for the reference.
Returns:
Reference<T>
object
An object with a single current: T property. Mutations to current do not trigger effects or computed updates.
import { useRef, useNode, useEvent } from 'fraxel/hooks'
import { PrimaryNode } from 'fraxel'

function ClickCounter() {
  const clickable = useNode(PrimaryNode.Clickable)
  const count = useRef(0) // not reactive — no re-render

  useEvent(clickable, 'clicked', () => {
    count.current++
    if (count.current >= 5) {
      console.log('Clicked 5 times!')
      count.current = 0
    }
  })

  return <clickable ref={clickable} size={[64, 64]} />
}

useChild

useChild<T extends PrimaryNode>(
  path: (string | symbol)[],
  type: T,
): NodeReference<T>
Retrieves a reference to a descendant node by navigating a path of node id values from the component root. The path is resolved after the node starts (on onFirst of the started event). useChild requires the component to have exactly one root node.
path
(string | symbol)[]
required
An ordered array of id values forming a path from the root node to the target child. Each entry corresponds to a child node’s id attribute.
type
PrimaryNode
required
The expected type of the target node. Used for type safety on the returned reference.
Returns:
NodeReference<T>
NodeReference<T>
A typed reference to the child node, populated after the component starts.
import { useChild, useEvent } from 'fraxel/hooks'
import { PrimaryNode } from 'fraxel'

function PlayerCard() {
  // Navigate: root → 'portrait' → 'avatar'
  const avatar = useChild(['portrait', 'avatar'], PrimaryNode.Sprite)

  useEvent(avatar, 'started', () => {
    console.log('Avatar sprite is ready')
  })

  return (
    <transform>
      <transform id="portrait">
        <sprite id="avatar" textureId={PLAYER_AVATAR} />
      </transform>
    </transform>
  )
}
useChild throws a HookRequiresNodeRootError if the component has more than one root node. Wrap siblings in a single <transform> or <group> if needed.

useScript

useScript<T extends FraxelScript<PrimaryNode>>(
  node: NodeReference<PrimaryNode>,
): SignalGetter<T | undefined>
Retrieves the FraxelScript instance attached to a node. Returns a reactive SignalGetter — the value updates reactively when the node’s script property changes. The signal value is undefined until the node is mounted.
node
NodeReference<PrimaryNode>
required
The node reference whose attached script you want to access.
Returns:
SignalGetter<T | undefined>
SignalGetter<T | undefined>
A reactive getter returning the script instance, or undefined if the node has no script or is not yet mounted.
import { useNode, useScript, useEffect } from 'fraxel/hooks'
import { PrimaryNode, FraxelScript } from 'fraxel'

class PlayerScript extends FraxelScript<PrimaryNode.Sprite> {
  health = 100

  takeDamage(amount: number) {
    this.health -= amount
    console.log('Health:', this.health)
  }
}

function HitDetector() {
  const sprite = useNode(PrimaryNode.Sprite)
  const script = useScript<PlayerScript>(sprite)

  useEffect(() => {
    const ps = script()
    if (ps) {
      ps.takeDamage(10)
    }
  })

  return <sprite ref={sprite} textureId={PLAYER} script={new PlayerScript()} />
}
See also: useNode, /guide/hooks, /guide/reactivity

Build docs developers (and LLMs) love