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

# Computed Properties: Automatic Derived State in Sprincul

> Register derived values with addComputedProp() and Sprincul automatically recomputes them when their dependencies change, batched with other state updates.

A computed property is a value that derives from one or more other state properties. Instead of manually updating a derived value every time its inputs change, you register it once and Sprincul keeps it current. Computed values are available on `this.state` just like regular state, so your binding callbacks and other methods can read them without any special handling.

## The `addComputedProp()` method

Call `this.addComputedProp(name, fn, dependencies)` from inside `beforeInit()` to register a computed property.

```
addComputedProp(name, fn, dependencies)
```

| Parameter      | Type        | Description                                                                    |
| -------------- | ----------- | ------------------------------------------------------------------------------ |
| `name`         | `string`    | The property name under which the computed value is available on `this.state`  |
| `fn`           | `() => any` | A function that returns the computed value. Called with the model as `this`.   |
| `dependencies` | `string[]`  | State property names that, when changed, trigger a recompute and DOM re-render |

The computed value is available immediately after registration via `this.state.<name>`.

## Dependencies and DOM re-renders

The `dependencies` array tells Sprincul which state properties this computed value depends on. When any dependency changes, Sprincul invalidates the computed value and schedules a DOM update for elements bound to it.

<Warning>
  Calling `addComputedProp()` without a `dependencies` array (or with an empty array) means the computed value will be accessible on `this.state`, but **bound elements will not re-render** when the value changes. Always provide dependencies when you need the DOM to stay in sync.
</Warning>

## Registering computed properties from `beforeInit()`

You must call `addComputedProp()` from inside `beforeInit()`. This is because Sprincul needs the computed property registered before it sets up bindings, and `beforeInit()` is the hook that runs at that point. Calling `addComputedProp()` outside `beforeInit()` — before the core is available — will throw an error.

## A full example: Totals

The `Totals` model computes a running total from `price` and `qty`. Both state properties are initialized in `beforeInit()`, and the computed `total` property is registered with both as dependencies. When either input changes, Sprincul recomputes `total` and updates the bound `<span>`.

```html theme={null}
<div data-model="Totals">
  <label for="price">Price</label>
  <input id="price" name="price" type="number" value="10" oninput="setPrice" />
  <label for="qty">Quantity</label>
  <input id="qty" name="qty" type="number" value="2" oninput="setQty" />
  <p>Total: <span data-bind-total="showTotal"></span></p>
</div>
```

```js theme={null}
// Totals.js
import { SprinculModel } from 'sprincul';

export default class Totals extends SprinculModel {
  beforeInit() {
    // Initialize state
    this.state.price = 10;
    this.state.qty = 2;

    // Register a computed property that depends on price and qty
    this.addComputedProp(
      'total',
      () => this.state.price * this.state.qty,
      ['price', 'qty']
    );
  }

  /** @param {InputEvent} e */
  setPrice(e) {
    this.state.price = Number(e.target.value || 0);
  }

  /** @param {InputEvent} e */
  setQty(e) {
    this.state.qty = Number(e.target.value || 0);
  }

  /** @param {HTMLElement} el */
  showTotal(el) {
    el.textContent = String(this.state.total);
  }
}
```

When the user changes either input:

1. The event handler updates `this.state.price` or `this.state.qty`.
2. Sprincul detects the dependency change and schedules an update for `total`.
3. On the next animation frame, `showTotal` is called with the bound `<span>`.
4. `this.state.total` returns the freshly computed value (`price * qty`), and the element's text is updated.

## Batching behavior

Computed property invalidations run in the same `requestAnimationFrame` batch as regular state updates. If you change multiple dependencies in the same synchronous block, the computed property recomputes once and the DOM updates once — not once per dependency change. This keeps rapid updates predictable and avoids redundant work.

```js theme={null}
// Both changes are batched — showTotal is called once, not twice
this.state.price = 20;
this.state.qty = 5;
```

<Tip>
  Computed properties work the same way as regular state when it comes to bindings. Use `data-bind-total="showTotal"` exactly as you would for any other state property.
</Tip>
