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 automaticallyAny 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>| Parameter | Type | Description |
|---|---|---|
computeFn | () => T | A 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 accessrecompute()
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" immediatelypeek() — 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 ignoredRead-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(); // 150Dynamic 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>
);
}