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.

Derived hooks compose Fraxel’s native hooks to provide domain-specific abstractions for common patterns. They don’t introduce new engine functionality — every derived hook is expressible using useSignal, useComputed, useEvent, and useNode directly. They exist to eliminate boilerplate for patterns that appear repeatedly in game components: toggling booleans on opposing events, mapping signal values through records, and wiring up physics and animation nodes with reactive state. All derived hooks are exported from fraxel/hooks alongside the core hooks.
Every node-specific derived hook (useClickable, useTimer, useRayCast, useCollider, useAnimation, useAudio) accepts an optional ref parameter. If you pass an existing NodeReference, the hook uses it as-is. If you omit the parameter, the hook creates a new NodeReference internally via usePartialNode. This lets you share a single node reference across multiple hooks or use the derived hook as a self-contained unit.

Quick Reference

HookDescriptionReturns
useConditionReactive boolean toggled by two opposing node eventsSignalGetter<boolean>
useMatchMaps a signal value to a record — like a reactive switchSignalGetter<U>
useWhenTernary expression for signalsSignalGetter<T>
useClickableClickable node with reactive hover state and mouse position{ ref, hovered, position }
useTimerTimer node with reactive time/progress and controls{ ref, time, progress, play, pause, stop }
useRayCastRayCast node with reactive detected state and collider{ ref, detected, collider }
useColliderCollider node with reactive collision state and collider set{ ref, colliding, detectedColliders }
useAnimationAnimationPlayer with reactive animation state and controls{ ref, animName, frameIndex, ended, play, stop, setNext }
useAudioAudioPlayer with reactive playing state and controls{ ref, playing, play, pause, stop }
usePartialNodeReturns the provided ref or creates a new oneNodeReference<T>

useCondition

useCondition<N extends PrimaryNode, A extends keyof Events<N>, D extends keyof Events<N>>(
  node: NodeReference<N>,
  agreeEvent: A,
  disagreeEvent: D,
  defaultValue?: boolean,
): SignalGetter<boolean>
Creates a reactive boolean that toggles between true and false based on two opposing events fired by the same node. Internally composes useSignal and two useEvent calls. Use it any time you need a boolean that mirrors an “on/off” pair of events — hover state, detection state, active zones, etc.
node
NodeReference<N>
required
The node that emits both events.
agreeEvent
A
required
The event name that sets the condition to true. Must be a valid event for the node type.
disagreeEvent
D
required
The event name that sets the condition to false. Must be a valid event for the node type.
defaultValue
boolean
The initial value before either event fires. Defaults to false.
Returns:
SignalGetter<boolean>
SignalGetter<boolean>
A reactive getter that returns true after the agreeEvent fires and false after the disagreeEvent fires.
import { useNode, useCondition, useComputed } from 'fraxel/hooks'
import { PrimaryNode } from 'fraxel'

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

  // true while the ray is intersecting an enemy collider
  const enemyDetected = useCondition(raycast, 'colliderEntered', 'colliderExited')

  // drive animation or color reactively
  const alertColor = useComputed(() =>
    enemyDetected() ? [1, 0, 0, 1] : [0, 1, 0, 1]
  )

  return (
    <transform>
      <rectangle size={[12, 12]} fillColor={alertColor} />
      <ray-cast
        ref={raycast}
        direction={[150, 0]}
        collidesWith={['enemy']}
      />
    </transform>
  )
}
See also: useSignal, useEvent

useMatch

useMatch<T extends number | string | symbol, K>(
  signal: SignalGetterLike<T>,
  record: Reactive<Record<T, K>>,
  defaultValue?: Reactive<K>,
): SignalGetter<K | undefined>
Creates a computed value by looking up the current signal value in a record — analogous to a reactive switch or match expression. Internally composes useComputed. If the current signal value is not a key in the record, defaultValue is returned (or undefined if not provided). Both record and defaultValue accept Reactive<T> — either a plain value or a SignalGetterLike, so the record itself can be reactive.
signal
SignalGetterLike<T>
required
A signal getter (or any zero-argument function) whose return value is used as the lookup key.
record
Reactive<Record<T, K>>
required
A record mapping possible signal values to output values. Can be a plain object or a signal getter returning an object.
defaultValue
Reactive<K>
The value to return when the signal value is not found in the record. Can be a plain value or a signal getter.
Returns:
SignalGetter<K | undefined>
SignalGetter<K | undefined>
A reactive getter reflecting the matched value, updating whenever the input signal changes.
import { useSignal, useMatch } from 'fraxel/hooks'

function Character() {
  const [state, setState] = useSignal<'idle' | 'walking' | 'attacking'>('idle')

  // Reactive animation name derived from state signal
  const animName = useMatch(state, {
    idle:      'idle-loop',
    walking:   'walk-cycle',
    attacking: 'attack-swing',
  }, /* defaultValue */ 'idle-loop')

  return (
    <transform>
      <animation-player
        animations={() => characterAnims}
        currentAnim={animName}
      />
      <clickable size={[32, 64]} onClick={() => setState('attacking')} />
    </transform>
  )
}

useWhen

useWhen<T>(
  signal: SignalGetterLike<boolean>,
  whenTrue: Reactive<T>,
  whenFalse: Reactive<T>,
): SignalGetter<T>
Creates a computed value that evaluates to whenTrue or whenFalse based on a boolean signal — a reactive ternary expression. Internally composes useComputed. Both whenTrue and whenFalse accept Reactive<T>, so they can be plain values or signal getters.
signal
SignalGetterLike<boolean>
required
The boolean signal controlling which value to return.
whenTrue
Reactive<T>
required
The value (or reactive getter) to return when the signal is true.
whenFalse
Reactive<T>
required
The value (or reactive getter) to return when the signal is false.
Returns:
SignalGetter<T>
SignalGetter<T>
A reactive getter that returns whenTrue() or whenFalse() based on the current signal value.
import { useNode, useCondition, useWhen } from 'fraxel/hooks'
import { PrimaryNode } from 'fraxel'

function HoverButton() {
  const clickable = useNode(PrimaryNode.Clickable)
  const hovered = useCondition(clickable, 'mouseEntered', 'mouseExited')

  // Brightness is 1.2 when hovered, 1.0 otherwise
  const brightness = useWhen(hovered, 1.2, 1.0)

  return (
    <sprite textureId={BUTTON_TEX} brightness={brightness}>
      <clickable ref={clickable} size={[80, 32]} />
    </sprite>
  )
}

useClickable

useClickable(clickable?: NodeReference<PrimaryNode.Clickable>): {
  ref: NodeReference<PrimaryNode.Clickable>
  hovered: SignalGetter<boolean>
  position: SignalGetter<Vector2>
}
Wraps a Clickable node with reactive hover state and mouse position tracking. Internally creates a NodeReference (or reuses a provided one), then composes useCondition for hovered and useEvent for position.
clickable
NodeReference<PrimaryNode.Clickable>
An optional existing NodeReference to the Clickable node. If omitted, a new reference is created.
Returns:
ref
NodeReference<PrimaryNode.Clickable>
The node reference to pass to the <clickable> element’s ref prop.
hovered
SignalGetter<boolean>
true while the pointer is over the clickable area; false otherwise.
position
SignalGetter<Vector2>
The current pointer position over the clickable area, updated on every mouseOver event.
import { useClickable, useComputed } from 'fraxel/hooks'

const BTN_TEXTURE = Symbol('button')

function Button({ onClick }: { onClick: () => void }) {
  const { ref, hovered } = useClickable()

  // Drive brightness reactively from hover state
  const brightness = useComputed(() => (hovered() ? 1.15 : 1.0))

  return (
    <sprite textureId={BTN_TEXTURE} brightness={brightness}>
      <clickable ref={ref} size={[96, 40]} onClick={onClick} />
    </sprite>
  )
}
See also: useCondition, /api/nodes-2d

useTimer

useTimer(timer?: NodeReference<PrimaryNode.Timer>): {
  ref: NodeReference<PrimaryNode.Timer>
  time: SignalGetter<number>
  progress: SignalGetter<number>
  play: (...args) => void
  pause: (...args) => void
  stop: (...args) => void
}
Wraps a Timer node with reactive time and progress signals plus imperative controls. time updates on every timeChanged event. progress is a computed value — time() / duration — clamped between 0 and 1 relative to the timer’s duration prop.
timer
NodeReference<PrimaryNode.Timer>
An optional existing NodeReference to the Timer node. If omitted, a new reference is created.
Returns:
ref
NodeReference<PrimaryNode.Timer>
The node reference to pass to the <timer> element’s ref prop.
time
SignalGetter<number>
The current elapsed time in seconds, updated reactively as the timer runs.
progress
SignalGetter<number>
A 01 ratio of elapsed time to total duration. Computed as time() / duration.
play
(...args) => void
Starts or resumes the timer. Delegates to the underlying Timer node’s play method.
pause
(...args) => void
Pauses the timer without resetting it.
stop
(...args) => void
Stops the timer and resets elapsed time to zero.
import { useTimer } from 'fraxel/hooks'

function AbilityCooldown() {
  const { ref, time, progress, play, stop } = useTimer()

  const handleActivate = () => {
    stop()
    play()
  }

  return (
    <transform>
      <timer ref={ref} duration={5} />
      {/* Progress bar: width shrinks from 100 → 0 as timer runs */}
      <rectangle
        size={[100 * (1 - progress()), 8]}
        fillColor={[0.2, 0.6, 1, 1]}
      />
      <clickable size={[48, 48]} onClick={handleActivate} />
    </transform>
  )
}
See also: /api/nodes-utility

useRayCast

useRayCast(raycast?: NodeReference<PrimaryNode.RayCast>): {
  ref: NodeReference<PrimaryNode.RayCast>
  detected: SignalGetter<boolean>
  collider: SignalGetter<Collider | null>
}
Wraps a RayCast node with reactive detection state. collider updates whenever the ray starts or stops intersecting a collider. detected is a computed boolean derived from collider() != null.
raycast
NodeReference<PrimaryNode.RayCast>
An optional existing NodeReference to the RayCast node. If omitted, a new reference is created.
Returns:
ref
NodeReference<PrimaryNode.RayCast>
The node reference to pass to the <ray-cast> element’s ref prop.
detected
SignalGetter<boolean>
true while the ray is intersecting at least one collider; computed as collider() != null.
collider
SignalGetter<Collider | null>
The most recently detected Collider instance, or null when nothing is intersected.
import { useRayCast, useComputed } from 'fraxel/hooks'

function Peashooter() {
  const { ref, detected, collider } = useRayCast()

  // Reactively display the name of the detected target
  const targetName = useComputed(() => collider()?.node.name ?? 'none')

  return (
    <transform>
      <ray-cast
        ref={ref}
        direction={[200, 0]}
        collidesWith={['zombie']}
      />
      <text text={targetName} position={[0, -20]} />
    </transform>
  )
}
See also: useCondition, /api/nodes-2d

useCollider

useCollider(collider?: NodeReference<PrimaryNode.Collider>): {
  ref: NodeReference<PrimaryNode.Collider>
  colliding: SignalGetter<boolean>
  detectedColliders: SignalGetter<Set<Collider>>
}
Wraps a Collider node with reactive collision tracking. Maintains a Set of all currently overlapping colliders — each entry or exit event creates a new Set instance so signal updates are triggered correctly. colliding is a computed signal derived as detectedColliders() != null. To check whether any collisions are active, test detectedColliders().size > 0 directly.
collider
NodeReference<PrimaryNode.Collider>
An optional existing NodeReference to the Collider node. If omitted, a new reference is created.
Returns:
ref
NodeReference<PrimaryNode.Collider>
The node reference to pass to the <collider> element’s ref prop.
colliding
SignalGetter<boolean>
Computed as detectedColliders() != null. Since detectedColliders is always a Set, use detectedColliders().size > 0 to test whether any colliders are currently overlapping.
detectedColliders
SignalGetter<Set<Collider>>
A reactive Set of all Collider instances currently overlapping this collider. Each entry/exit creates a new Set instance to trigger reactive updates.
import { useCollider, useEffect } from 'fraxel/hooks'
import { shapes } from 'fraxel'

function DamageZone() {
  const { ref, colliding, detectedColliders } = useCollider()

  useEffect(() => {
    if (colliding()) {
      for (const other of detectedColliders()) {
        console.log('Zone touching:', other.node.tag)
      }
    }
  })

  return (
    <collider
      ref={ref}
      shape={shapes.rect(80, 80)}
      group={['zone']}
      collidesWith={['player']}
    />
  )
}
See also: /api/nodes-2d

useAnimation

useAnimation(anim?: NodeReference<PrimaryNode.AnimationPlayer>): {
  ref: NodeReference<PrimaryNode.AnimationPlayer>
  animName: SignalGetter<string | null>
  frameIndex: SignalGetter<number>
  ended: SignalGetter<boolean>
  play: (...args) => void
  stop: (...args) => void
  setNext: (...args) => void
}
Wraps an AnimationPlayer node with reactive animation state and imperative controls. All signals update as animation events fire on the node.
anim
NodeReference<PrimaryNode.AnimationPlayer>
An optional existing NodeReference to the AnimationPlayer node. If omitted, a new reference is created.
Returns:
ref
NodeReference<PrimaryNode.AnimationPlayer>
The node reference to pass to the <animation-player> element’s ref prop.
animName
SignalGetter<string | null>
The name of the currently playing animation, or null when the animation is stopped. Updates on animationChanged and cleared on animationStopped.
frameIndex
SignalGetter<number>
The zero-based index of the currently displayed frame. Updates on every animationIndexChanged event.
ended
SignalGetter<boolean>
true after the current animation reaches its last frame (animationEnded), reset to false when a new animation starts (animationChanged). Composed via useCondition.
play
(animName?: string) => void
Starts playback. Delegates to the underlying AnimationPlayer.play method.
stop
(...args) => void
Stops playback and clears the current animation.
setNext
(animName?: string) => void
Queues an animation to play after the current one finishes. Delegates to AnimationPlayer.setNext.
import { useAnimation, useSignal, useEffect } from 'fraxel/hooks'

const CHARACTER_ANIMS = Symbol('anims')

function Fighter() {
  const { ref, animName, ended, play, setNext } = useAnimation()
  const [attacking, setAttacking] = useSignal(false)

  useEffect(() => {
    if (attacking()) {
      play('attack')
      setNext('idle') // auto-return to idle
    } else {
      play('idle')
    }
  })

  useEffect(() => {
    if (ended() && animName() === 'attack') {
      setAttacking(false)
    }
  })

  return (
    <transform>
      <animation-player
        ref={ref}
        animations={() => characterAnims}
        currentAnim="idle"
      />
      <clickable size={[48, 64]} onClick={() => setAttacking(true)} />
    </transform>
  )
}
See also: useCondition, /api/nodes-2d

useAudio

useAudio(audio?: NodeReference<PrimaryNode.AudioPlayer>): {
  ref: NodeReference<PrimaryNode.AudioPlayer>
  playing: SignalGetter<boolean>
  play: (...args) => void
  pause: (...args) => void
  stop: (...args) => void
}
Wraps an AudioPlayer node with a reactive playing boolean and imperative playback controls. playing is composed via useCondition — it becomes true on the started event and false on the ended event.
audio
NodeReference<PrimaryNode.AudioPlayer>
An optional existing NodeReference to the AudioPlayer node. If omitted, a new reference is created.
Returns:
ref
NodeReference<PrimaryNode.AudioPlayer>
The node reference to pass to the <audio-player> element’s ref prop.
playing
SignalGetter<boolean>
true while audio is playing. Toggles reactively between started and ended events.
play
(...args) => void
Starts or resumes audio playback.
pause
(...args) => void
Pauses playback without resetting position.
stop
(...args) => void
Stops playback and resets position.
import { useAudio, useClickable, useComputed } from 'fraxel/hooks'

const MUSIC_ID = Symbol('bg-music')
const BTN_ID   = Symbol('play-btn')

function MusicToggle() {
  const { ref, playing, play, stop } = useAudio()
  const { ref: btn, hovered } = useClickable()

  const btnBrightness = useComputed(() => (hovered() ? 1.1 : 1.0))
  const label = useComputed(() => (playing() ? '⏹ Stop' : '▶ Play'))

  return (
    <transform>
      <audio-player ref={ref} soundId={MUSIC_ID} loop />
      <sprite ref={btn} textureId={BTN_ID} brightness={btnBrightness}>
        <clickable
          size={[80, 32]}
          onClick={() => (playing() ? stop() : play())}
        />
      </sprite>
      <text text={label} position={[0, 40]} />
    </transform>
  )
}
See also: useCondition, /api/nodes-utility

usePartialNode

usePartialNode<T extends PrimaryNode>(
  type: T,
  node?: NodeReference<T>,
): NodeReference<T>
A low-level utility used internally by all node-specific derived hooks. If a NodeReference is provided and is an instance of NodeReference, it is returned as-is. Otherwise, a new NodeReference is created via useNode. This is what powers the optional ref parameter pattern throughout the derived hook API.
usePartialNode is an internal implementation detail and is not exported from fraxel/hooks. It is not part of the public API. The example below shows how it is used internally when building custom derived hooks in the same package.
type
PrimaryNode
required
The node type to create if no existing reference is provided.
node
NodeReference<T>
An optional existing NodeReference to reuse. If provided and is a NodeReference instance, returned directly without calling useNode.
Returns:
NodeReference<T>
NodeReference<T>
Either the provided reference or a freshly created one.
// usePartialNode is an internal utility — usage inside the fraxel package:
import { NodeReference, useNode } from '../use-node.js'
import { PrimaryNode } from '../../nodes/index.js'

// Building a custom derived hook that optionally accepts a ref
function useMyCustomClickable(existing?: NodeReference<PrimaryNode.Clickable>) {
  // Reuse existing ref or create a new one (mirrors usePartialNode behaviour)
  const ref = existing instanceof NodeReference
    ? existing
    : useNode(PrimaryNode.Clickable)

  // ...compose useEvent, useSignal, etc.

  return { ref }
}
usePartialNode is an internal implementation detail of the derived hook system and is not part of the public fraxel/hooks API. In your own game code, use one of the higher-level hooks (useClickable, useTimer, etc.) directly. If you are writing a custom derived hook, replicate the pattern shown above using useNode and instanceof NodeReference.
See also: useNode, /api/hooks-core, /guide/hooks, /guide/reactivity

Build docs developers (and LLMs) love