Reactivity is the heart of Fraxel’s developer experience. Rather than re-rendering a Virtual DOM tree, Fraxel uses signals — tiny reactive primitives that hold a value and notify subscribers when it changes. When a signal changes, only the exact canvas property bound to it is updated. No diffing, no tree traversal, no frame budget wasted on unchanged nodes. The result is smooth 60 FPS updates driven entirely by direct, surgical property mutations.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.
Signals
A signal is an instance of theSignal<T> class. It holds a value, tracks subscribers, and notifies them synchronously whenever the value is set to something new.
useSignal(initialValue), which wraps the Signal class and ties cleanup to the component’s node lifecycle:
The Getter is a Function
useSignal returns a tuple: [getter, setter]. The getter is a callable function — you must call it as health(), not reference it as health. This is what allows the reactivity system to automatically track which computations depend on which signals.
getter.value() bypasses dependency tracking. Use it when you want to read a signal inside a computed or effect without creating a reactive dependency.Auto-Computed JSX Props
Any JSX prop that accepts aReactive<T> type can receive either a static value or a function. When you pass a function (including an arrow function or a signal getter), Fraxel automatically tracks which signals are accessed inside it and re-applies the prop whenever any of those signals change.
Static prop — set once at mount, never updates:
health signal changes:
setHealth(0) is called, only the grayscale and opacity properties on that specific Sprite node are updated. Nothing else in the tree is touched.
This pattern works for any prop typed as Reactive<T>:
Computed Values
useComputed(fn) creates a derived signal — a read-only signal whose value is automatically recomputed whenever any signal accessed inside fn changes. It returns a SignalGetter<T> that can itself be passed as a reactive JSX prop.
useComputed cleans up all internal subscriptions when the node is destroyed, preventing memory leaks.
useEffect
useEffect(fn) runs a side-effect function on mount and re-runs it whenever any signal accessed inside fn changes. Unlike reactive props — which only update a single node property — useEffect can run arbitrary code in response to state changes.
Batching
If multiple signals change synchronously,useEffect runs only once — after all changes are applied. Re-executions are deferred via queueMicrotask and run before the next animation frame.
Batching ensures the canvas always sees a consistent game state. You can safely update multiple signals in a single event handler, physics step, or timer callback without triggering redundant work.
Signal Class API
For advanced cases you can useSignal<T> directly — useful for signals that live outside a component, or when you need manual subscription control.
| Member | Type / Signature | Description |
|---|---|---|
value (get) | T | Get the current value |
value (set) | (val: T) => void | Set the value and notify all subscribers (skips if unchanged) |
getter | SignalGetter<T> | Callable getter that registers the signal as a dependency when called |
setter | SignalSetter<T> | Callable setter equivalent to signal.value = val |
sub(fn) | (fn: (val: T) => void) => void | Subscribe a listener |
unsub(fn) | (fn: (val: T) => void) => void | Remove a specific listener |
clearSubs() | () => void | Remove all listeners |
Signal is identity-safe — setting the same value twice does not trigger subscribers:
Reactive vs Static Props
Use this decision guide when writing JSX props:| Scenario | Use | Example |
|---|---|---|
| Value never changes after mount | Static value | brightness={1.2} |
| Value changes based on game state | Arrow function / SignalGetter | brightness={() => hp() > 50 ? 1 : 0.6} |
| Multiple props derive from the same signal | useComputed | const alpha = useComputed(() => hp() / 100) |
| Need to run code (not just update a prop) on change | useEffect | Logging, spawning, scene transitions |
| Need the latest value without tracking deps | getter.value() | Inside a useEffect cleanup, or a plain callback |
How Dependency Tracking Works
When aSignalGetter is called inside a tracked context (a useEffect, useComputed, or a reactive JSX prop function), the SignalRegister records that signal as a dependency. After evaluation, the system subscribes the effect or computed to each discovered signal.
getter.value() instead of getter() skips tracking — it bypasses SignalRegister.register.
Related Pages
Hooks
useSignal, useComputed, useEffect, and all other hooks
Nodes
Reactive props on every JSX node type
API: Hooks Core
Full TypeScript signatures for the reactive hooks
Concepts
Scene tree, lifecycle, and rendering architecture