@juo/blocks is reactive end-to-end. Block props, service state, theme tokens, and workflow status are all reactive signals — when one changes, the renderers that depend on it re-run automatically.
One reactivity model, many frameworks
@juo/blocks exports a set of reactive primitives — signal, computed, effect, untracked — that work the same way regardless of which renderer you use. The API follows the TC39 Signals proposal, so the mental model will feel familiar. Each renderer bridges these primitives in the way that feels most natural for that framework:
| Renderer | Pattern |
|---|---|
| Vanilla | signal(), effect(), computed() directly |
| Vue | Signals appear as Refs through the useContext proxy — use them like any Vue ref |
| React | useSignal(signal) returns the current value and re-renders on change |
| Preact | useSignal(signal) — same shape as React, Preact’s hooks runtime |
Core primitives
These are re-exported from@juo/blocks for use in vanilla renderers, services, and tests.
signal(initialValue)
A writable observable. Reads inside an effect or computed register a dependency automatically.
computed(fn)
A read-only derived value, recomputed lazily.
effect(fn) + untracked(fn)
effect runs the callback immediately and re-runs it whenever a signal it read from changes. untracked reads a signal value without registering it as a dependency — useful when the read is incidental to the work.
Where signals show up
Signals appear in three places:- Service state. Every service exposes its mutable state as signals —
subscriptionService.current,workflowService.canRespond,customerService.current. See Services. - Theme tokens. The active theme palette is a signal, so blocks re-render when the merchant changes the theme.
- Block props. Inside renderers,
block.propsis reactive — wrap reads ineffect()to keep the DOM in sync.
Vanilla example
A renderer that prints the current customer’s name and updates whenever the service signal changes:customer.getCurrent()), the signal updates and the <p> re-renders. No manual subscription, no glue code.
Framework bridges
Framework adapters handle the bridging. Callingsignal.value manually inside components is not required:
- Vue.
useContext()returns a proxy that converts eachSignal<T>field into aRef<T>. - React / Preact.
useSignal(signal)reads the value and subscribes the component to changes.