The `computed()` function is used for derived data: ```tsx import { computed, Default, NAME, pattern, UI } from "commontools"; interface Item { title: string; done: Default; category: Default; } interface Input { items: Default; } export default pattern(({ items }) => { // Any values mentioned in a computed() are automatically closed-over. const grouped = computed(() => { const groups: Record = {}; for (const item of items) { const cat = item.category || "Other"; if (!groups[cat]) groups[cat] = []; groups[cat].push(item); } return groups; }); const categories = computed(() => Object.keys(grouped).sort()); return { [NAME]: "By Category", [UI]: (
{categories.map((cat) => (

{cat}

{(grouped[cat] ?? []).map((item) => ( {item.title} ))}
))}
), items, }; }); ``` --- ### When to Use computed() Use `computed()` **outside of JSX** for reactive transformations. **Use `computed()` for dynamic `[NAME]` values:** When deriving `[NAME]` from input props or other reactive values, wrap it in `computed()`: ```tsx // WRONG - input props are reactive, can't access at init time export default pattern(({ deck }) => ({ [NAME]: `Study: ${deck.name}`, // Error: reactive reference outside context ... })); // CORRECT - computed() creates a reactive context export default pattern(({ deck }) => ({ [NAME]: computed(() => `Study: ${deck.name}`), ... })); ``` Static strings like `[NAME]: "My Pattern"` don't need `computed()`. **Never wrap JSX in `computed()`** - the transformer automatically handles reactivity in JSX expressions. The compiler analyzes your JSX and wraps reactive expressions in `derive()` calls behind the scenes, so explicit `computed()` is unnecessary and can cause issues. Similarly, ternaries in JSX are automatically converted to `ifElse()` - see `docs/common/patterns/conditional.md`. ```tsx // ❌ WRONG - Don't wrap JSX in computed() {computed(() => (
...
))} // ✅ RIGHT - Just use the value directly in JSX
...
``` Example of correct usage: ```tsx import { computed, UI, Writable } from 'commontools'; interface Item { title: string; } const searchQuery = Writable.of(""); declare const items: Item[]; declare const groupedItems: Record; // ✅ Use computed() outside JSX const filteredItems = computed(() => { const query = searchQuery.get().toLowerCase(); return items.filter(item => item.title.toLowerCase().includes(query)); }); const itemCount = computed(() => items.length); const categories = computed(() => { return Object.keys(groupedItems).sort(); }); // Then use the computed values in JSX const result = { [UI]: (
Total: {itemCount}
{filteredItems.map(item =>
{item.title}
)} {categories.map(cat =>

{cat}

)}
), }; ``` ### Side Effects in computed() If your `computed()` has side effects (like setting another cell), they should be idempotent. Non-idempotent side effects cause the scheduler to re-run repeatedly until it hits the 101-iteration limit. ```typescript import { computed, Writable } from 'commontools'; interface Item {} declare const items: Item[]; const logArray = Writable.of<{ timestamp: number }[]>([]); const cacheMap = Writable.of>({}); // ❌ Non-idempotent - appends on every run const badComputed = computed(() => { const current = logArray.get(); logArray.set([...current, { timestamp: Date.now() }]); // Grows forever return items.length; }); // ✅ Idempotent - check-before-write with deterministic key const goodComputed = computed(() => { const current = cacheMap.get(); const key = `items-${items.length}`; if (!(key in current)) { cacheMap.set({ ...current, [key]: Date.now() }); } return items.length; }); ``` The scheduler re-runs computations when their dependencies change. If a computation modifies a cell it depends on, it triggers itself. With idempotent operations, the second run produces no change, so the system settles. Prefer using handlers for mutations instead of side effects in `computed()`.