@warmjs/signals
v1.0.0
Published
Minimal fine-grained reactivity (signals + effects) for WarmJS. Zero-dependency.
Downloads
417
Maintainers
Readme
@warmjs/signals
Minimal fine-grained reactivity for WarmJS. Zero-dependency. Designed to pair with @warmjs/core (direct-DOM JSX with no virtual DOM).
Philosophy (Solid-like, simplified): signals are the source of truth, effects re-run only when a signal they read changes. No component re-render, no diffing.
Install
npm install @warmjs/signalsUsage
import { createSignal, createEffect, batch, bindText } from "@warmjs/signals";
// State
const [count, setCount] = createSignal(0);
// Side effect — re-runs when `count` changes
const dispose = createEffect(() => {
console.log("count =", count());
});
setCount(1); // logs again
setCount((p) => p + 1);
// Batch multiple writes into one flush
batch(() => {
setCount(10);
setCount(11);
}); // effect runs once
dispose(); // stop tracking + run cleanupWith @warmjs/core JSX
warm-core snapshots {value} into a static text node. Use bindText() for a live node:
/** @jsx h */
import { h } from "@warmjs/core";
import { createSignal, bindText } from "@warmjs/signals";
const [label, setLabel] = createSignal("Opacity");
const node = <span>{bindText(label)}</span>;
setLabel("Brightness"); // span updates, no rebuildCleanup
import { createEffect, onCleanup } from "@warmjs/signals";
createEffect(() => {
const timer = setInterval(() => console.log("tick"), 1000);
onCleanup(() => clearInterval(timer));
});Derived values
import { createSignal, createMemo } from "@warmjs/signals";
const [w, setW] = createSignal(1200);
const [h, setH] = createSignal(600);
const area = createMemo(() => w() * h());
console.log(area()); // 720000Escape tracking
import { untrack } from "@warmjs/signals";
createEffect(() => {
console.log("tracked:", count());
untrack(() => console.log("not tracked:", other()));
});Shared reset (one button, many targets)
import { createSignal } from "@warmjs/signals";
import { createResetScope } from "@warmjs/signals";
const [opacity, setOpacity] = createSignal(100);
const [rotate, setRotate] = createSignal(0);
const [blur, setBlur] = createSignal(0);
const scope = createResetScope();
const un1 = scope.register(() => setOpacity(100));
scope.register(() => setRotate(0));
scope.register(() => setBlur(0));
scope.reset(); // all three restored in a single batch
un1(); // unregister on unmount/disposeAPI
| Export | Signature | Description |
|---|---|---|
| createSignal | <T>(initial: T) => [get, set] | set accepts a value or updater, skips notify on Object.is equality. |
| createEffect | (fn: () => void) => dispose | Runs immediately, re-runs on dependency change. |
| createMemo | (fn: () => T) => getter | Cached derived getter. |
| onCleanup | (fn: () => void) => void | Cleanup for the current effect pass. |
| untrack | (fn: () => T) => T | Run without collecting dependencies. |
| batch | (fn: () => void) => void | Coalesce writes; each effect runs at most once. |
| bindText | (getter: () => unknown) => Text | Live text node for warm-core JSX. |
| createResetScope | () => ResetScope | { register, reset, size } — one Reset button restores N targets in one batch. |
What it is NOT
- No component model, no hooks rules, no scheduler priorities.
- No store / router — Pixorig keeps those in its own core.
- Effects run synchronously (except inside
batch).
License
MIT.
