> ## Documentation Index
> Fetch the complete documentation index at: https://juo.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Reactivity

> Signal-based reactive state that flows through every framework adapter.

`@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](https://github.com/tc39/proposal-signals), 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 `Ref`s 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                   |

A service can update a signal from anywhere — a fetch callback, a websocket, a workflow response — and the change propagates through **every** renderer's output without manual wiring.

## 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.

```ts theme={null}
import { signal } from "@juo/blocks";

const count = signal(0);
count.value++;            // triggers dependents
console.log(count.value); // 1
```

### `computed(fn)`

A read-only derived value, recomputed lazily.

```ts theme={null}
import { signal, computed } from "@juo/blocks";

const price = signal(1000);
const tax = computed(() => price.value * 0.21);

tax.value; // 210
```

### `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.

```ts theme={null}
import { effect, untracked } from "@juo/blocks";

effect(() => {
  const status = subscription.status.value;  // tracked
  untracked(() => log("status changed", status));
});
```

## Where signals show up

Signals appear in three places:

1. **Service state.** Every service exposes its mutable state as signals — `subscriptionService.current`, `workflowService.canRespond`, `customerService.current`. See [Services](/docs/blocks/concepts/services).
2. **Theme tokens.** The active theme palette is a signal, so blocks re-render when the merchant changes the theme.
3. **Block props.** Inside renderers, `block.props` is reactive — wrap reads in `effect()` to keep the DOM in sync.

## Vanilla example

A renderer that prints the current customer's name and updates whenever the service signal changes:

```ts theme={null}
import { defineBlock, effect, untracked, injectContext } from "@juo/blocks";
import { CustomerServiceContext } from "@juo/blocks";

defineBlock("Greeting", {
  group: "theme",
  schema: { /* ... */ } as const,
  initialValue: () => ({ props: {} }),
  renderer(block) {
    const el = document.createElement("p");
    const customer = injectContext(el, CustomerServiceContext);

    effect(() => {
      const name = customer.current.value?.name ?? "guest";
      untracked(() => {
        el.textContent = `Welcome back, ${name}`;
      });
    });

    return el;
  },
});
```

When the host fetches the customer (`customer.getCurrent()`), the signal updates and the `<p>` re-renders. No manual subscription, no glue code.

## Framework bridges

Framework adapters handle the bridging. Calling `signal.value` manually inside components is not required:

* **Vue.** `useContext()` returns a proxy that converts each `Signal<T>` field into a `Ref<T>`.
* **React / Preact.** `useSignal(signal)` reads the value and subscribes the component to changes.

See the [Vue](/docs/blocks/frameworks/vue), [React](/docs/blocks/frameworks/react), and [Preact](/docs/blocks/frameworks/preact) guides for the framework-specific shape.

## Creating reactive state in a service

When a service needs to expose reactive values, create signals internally and return them:

```ts theme={null}
import { signal, computed } from "@juo/blocks";

export function createCartService() {
  const items = signal<CartItem[]>([]);
  const total = computed(() =>
    items.value.reduce((sum, i) => sum + i.price * i.quantity, 0),
  );

  return {
    items,
    total,
    add(item: CartItem) { items.value = [...items.value, item]; },
  };
}
```

Consumers in any framework see the same reactive values and remain synchronized without additional wiring.
