StunkStunk
Core Concepts

Computed

Derive reactive state from multiple chunks using computed().

computed() creates a derived chunk that automatically tracks its dependencies and recomputes whenever any of them change. Dependencies are detected by monitoring which chunks call .get() during execution — no dependency arrays needed.

import { chunk, computed } from "stunk";

const price = chunk(100);
const quantity = chunk(3);

const total = computed(() => price.get() * quantity.get());

total.get(); // 300

quantity.set(5);
total.get(); // 500 — recomputed automatically

Any chunk whose .get() is called inside the compute function becomes a dependency automatically. Use .peek() to read a value without tracking it.


API

computed(computeFn): Computed<T>
ParameterTypeDescription
computeFn() => TA function that derives the value. Any .get() call is tracked

The Computed interface

A computed chunk extends ReadOnlyChunk<T> with two additional methods:

isDirty()

Returns true if any dependency has changed since the last computation.

const total = computed(() => price.get() * quantity.get());

total.isDirty(); // false

price.set(200);
total.isDirty(); // true — not yet recomputed

total.get();
total.isDirty(); // false — recomputed on access

recompute()

Forces recomputation immediately. Normally not needed — Stunk recomputes automatically on get() or when subscribers are active.

total.recompute();

Lazy vs eager evaluation

Computed values are lazy by default — they only recompute when accessed via get(). When a computed has active subscribers, it switches to eager mode and recomputes immediately when any dependency changes.

const total = computed(() => price.get() * quantity.get());

// No subscribers — lazy, recomputes on get()
price.set(200);
total.isDirty(); // true
total.get(); // recomputes now → 600

// With a subscriber — eager, recomputes immediately on change
const unsub = total.subscribe((v) => console.log("total:", v));
price.set(50); // logs: "total: 150" immediately

peek() — non-tracking reads

Use .peek() inside a compute function to read a value without registering it as a dependency:

const taxRate = chunk(0.1);
const subtotal = chunk(200);

const total = computed(() => subtotal.get() * (1 + taxRate.peek()));
// only recomputes when subtotal changes — taxRate changes are ignored

Read-only

Computed chunks are read-only. Calling set() or reset() will throw:

total.set(999); // ❌ TypeError — set does not exist on Computed<T>
total.reset(); // ❌ TypeError — reset does not exist on Computed<T>

To change the result, update the source chunks:

price.set(50);
total.get(); // 150

Dynamic dependencies

Dependencies are re-tracked on every recompute — so conditional logic works correctly:

const useDiscount = chunk(true);
const base = chunk(100);
const discount = chunk(0.2);

const price = computed(() =>
  useDiscount.get() ? base.get() * (1 - discount.get()) : base.get(),
);

price.get(); // 80 (discount applied)

useDiscount.set(false);
price.get(); // 100 (discount branch not taken — discount no longer a dependency)

Performance

Stunk uses strict equality for primitives and shallow equality for objects to avoid unnecessary subscriber notifications:

  • Primitives — only notifies if newValue !== cachedValue
  • Objects — only notifies if !shallowEqual(newValue, cachedValue)

TypeScript

Types are inferred automatically from the compute function:

const price = chunk(100); // Chunk<number>
const quantity = chunk(3); // Chunk<number>

const total = computed(() => price.get() * quantity.get());
// Computed<number>

const label = computed(() => `Total: ₦${total.get()}`);
// Computed<string>

In React

Define outside the component, then read with useChunkValue():

import { chunk, computed } from "stunk";
import { useChunk, useChunkValue } from "stunk/react";

const price = chunk(100);
const quantity = chunk(2);
const total = computed(() => price.get() * quantity.get());

function OrderSummary() {
  const [qty, setQty] = useChunk(quantity);
  const totalValue = useChunkValue(total);

  return (
    <div>
      <p>Quantity: {qty}</p>
      <p>Total: ${totalValue}</p>
      <button onClick={() => setQty((n) => n + 1)}>Add</button>
    </div>
  );
}

What's next?

On this page