| name | mongez-atom-derived-atoms |
| description | How to create computed atoms with `derive()` — auto-tracked dependencies, conditional reads, chained derivations, and cleanup.
TRIGGER when: code imports or calls `derive`, uses `DeriveGetter` / `DeriveOptions` types, or builds a value from other atoms via a `get` argument; user asks "how do I create a computed atom that updates with its sources", "how do I auto-track dependencies", or "how do I clean up a derived atom"; `import { derive } from "@mongez/atom"`.
SKIP: writable base atoms (use `mongez-atom-atoms` / `mongez-atom-defining-atoms`); array verbs over a collection (use `mongez-atom-collections`); React-side hook integration (lives in `@mongez/react-atom`); the sibling `mongez-atom-derived` skill — only one of the two should fire for the same request.
|
Derived Atoms
When to use
Load this skill when the user:
- Needs a value that is computed from one or more other atoms
- Uses
derive() from @mongez/atom
- Asks about auto-tracked dependencies (Jotai/MobX-computed style)
- Needs a derived value that works with React hooks in
@mongez/react-atom
What derive() does
derive(key, compute) creates a normal Atom<T> whose value is produced by a pure compute function. The function receives a get helper; any atom read through get becomes a tracked dependency. When any dependency changes, the compute function re-runs and the derived atom's value updates automatically.
The returned atom is a full Atom<T> — you can subscribe to it with onChange, read .value, use it in React with useValue(derived), even chain it into another derive.
Basic example
import { createAtom, derive } from "@mongez/atom";
const firstName = createAtom({ key: "first", default: "Ada" });
const lastName = createAtom({ key: "last", default: "Lovelace" });
const fullName = derive("fullName", get =>
`${get(firstName)} ${get(lastName)}`
);
fullName.value;
firstName.update("Grace");
fullName.value;
Multiple sources
const priceAtom = createAtom({ key: "price", default: 100 });
const quantityAtom = createAtom({ key: "quantity", default: 3 });
const taxRateAtom = createAtom({ key: "taxRate", default: 0.1 });
const totalAtom = derive("total", get => {
const subtotal = get(priceAtom) * get(quantityAtom);
return subtotal * (1 + get(taxRateAtom));
});
totalAtom.value;
quantityAtom.update(5);
totalAtom.value;
Conditional reads — dynamic dependency graph
The dependency set is re-evaluated on every run. Branches that are not taken on a given run do not create a subscription for that run:
const branch = createAtom({
key: "branch",
default: "first" as "first" | "last",
});
const selected = derive("selected", get =>
get(branch) === "first" ? get(firstName) : get(lastName)
);
selected.value;
branch.update("last");
selected.value;
When branch flips to "last", selected unsubscribes from firstName and subscribes to lastName. Memory-safe: no stale subscriptions accumulate.
Chaining derivations
A derived atom that reads another derived atom recomputes when either level changes:
const cartAtom = atomCollection<{ price: number; qty: number }>({ key: "cart3" });
const promoAtom = createAtom({ key: "promo", default: 0 });
const subtotal = derive("subtotal", get =>
get(cartAtom).reduce((s, i) => s + i.price * i.qty, 0)
);
const finalPrice = derive("finalPrice", get =>
get(subtotal) * (1 - get(promoAtom))
);
cartAtom.push({ price: 50, qty: 2 });
finalPrice.value;
promoAtom.update(0.2);
finalPrice.value;
Subscribing to a derived atom
Derived atoms are normal Atom<T> instances; all subscription APIs work:
const sub = fullName.onChange((next, prev) => {
console.log("Name changed:", next);
});
sub.unsubscribe();
Cleanup / destroying a derived atom
When you destroy() a derived atom, it automatically unsubscribes from all tracked dependencies. No manual cleanup is needed:
fullName.destroy();
Signature reference
function derive<T>(
key: string,
compute: (get: DeriveGetter) => T,
options?: { register?: boolean },
): Atom<T>;
type DeriveGetter = <V>(atom: Atom<V, any>) => V;
Key pitfalls
- Never write to a derived atom directly.
derivedAtom.update(x) works at runtime but the next dependency change will overwrite it. Use a regular createAtom if you need writable state.
- Object return values always re-trigger because the compute function builds a new object reference every call. For primitives, the atom's
update() short-circuits on === equality — so derive("flag", get => get(a) > 0) will only notify subscribers when the boolean value actually changes.
- Errors in compute are surfaced asynchronously via
queueMicrotask. The atom keeps its previous value on error. This prevents one broken derivation from crashing the source atom's update cycle — but errors may appear in the console later than expected.
- Keys must be unique just like any atom key. Use a namespaced key:
"cart.totalPrice" not just "total".
- Initial computation is eager.
derive(...) runs the compute function once immediately on creation. If a dependency atom doesn't exist yet at that moment, it will be undefined. Register all source atoms before their derived counterparts.