| name | mongez-atom-derived |
| description | How to create computed atoms with `derive()` — auto-tracked dependencies, dynamic dep graphs, chaining, and React consumption.
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/derived atom", "how do I auto-track atom dependencies", or "how do I chain derived atoms"; `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 `useValue` / `useState` wiring (lives in `@mongez/react-atom`); the sibling `mongez-atom-derived-atoms` skill — only one of the two should fire for the same request.
|
Derived atoms
derive(key, compute) creates a computed atom whose value is built from other atoms. Dependencies are tracked automatically: whichever atoms the compute function reads via its get argument become dependencies. When any of them change, the derived atom recomputes and notifies its own subscribers.
Signature
function derive<T>(
key: string,
compute: (get: DeriveGetter) => T,
options?: { register?: boolean },
): Atom<T>
type DeriveGetter = <V>(atom: Atom<V, any>) => V;
The returned atom is a normal Atom<T> — every consumer pattern from @mongez/react-atom (useValue, useState, use(key), useWatch, etc.) and from the core (onChange, watch, destroy) works on it.
Behavior
- Eager initial compute. The function runs once on creation to seed the value and discover the first set of dependencies.
- Eager recompute. When any tracked dependency changes (via
update / change / merge), the function runs again and the result is pushed through the atom's normal update flow — every subscriber re-renders.
- Dynamic dependency graph. Conditional reads (
if (get(branch) === "a") return get(a); else return get(b);) work correctly. After each recompute the engine diffs the new dep set against the old one, unsubscribes from atoms no longer read, and subscribes to newly-seen ones.
- Chaining propagates. A derived atom can be a dependency of another derived atom. Updates fan out through the chain.
- Error isolation. If
compute throws, the previous value is kept and the error is surfaced asynchronously (via a microtask re-throw) so the source atom's update cycle isn't broken.
- Destroy unsubscribes deps.
derivedAtom.destroy() drops every dependency subscription before clearing the registry entry.
Examples
Simple
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;
Filtered view
type Todo = { id: number; text: string; done: boolean };
const todos = atomCollection<Todo>({ key: "todos" });
const filter = createAtom({ key: "filter", default: "all" as "all" | "active" | "done" });
const visible = derive("visible-todos", get => {
const f = get(filter);
const list = get(todos);
if (f === "active") return list.filter(t => !t.done);
if (f === "done") return list.filter(t => t.done);
return list;
});
Cross-atom gate (the killer use case)
const canCheckout = derive("canCheckout", get =>
get(cart).length > 0 &&
get(userAtom).loggedIn &&
!get(checkoutLoading).isLoading,
);
if (canCheckout.value) {
}
Conditional reads (dynamic dep graph)
const branch = createAtom({ key: "branch", default: "a" as "a" | "b" });
const a = createAtom({ key: "av", default: "from-a" });
const b = createAtom({ key: "bv", default: "from-b" });
const selected = derive("selected", get =>
get(branch) === "a" ? get(a) : get(b),
);
b.update("changed");
branch.update("b");
b.update("changed-again");
Chained derivations
const a = createAtom({ key: "a", default: 2 });
const doubled = derive("doubled", get => get(a) * 2);
const quadrupled = derive("quadrupled", get => get(doubled) * 2);
a.update(5);
doubled.value;
quadrupled.value;
React consumption
Through @mongez/react-atom, derived atoms get the same hooks as any other atom:
function Counter() {
const count = quadrupled.useValue();
return <span>{count}</span>;
}
For object-valued derived atoms, use(key) still works to scope subscription to one field.
Gotchas
- Don't write to derived atoms. Calling
update / merge / change on the returned atom works, but the next dependency change will overwrite your write. If you need writable state, use a regular atom.
- Don't depend on yourself. A derive that reads its own atom inside
compute creates an infinite loop. The engine doesn't detect cycles — keep dependency graphs acyclic.
- Throws are asynchronous. Errors inside
compute are caught and re-thrown in a microtask so the upstream update cycle isn't broken. Catch them with a process.on("uncaughtException") handler or browser window.onerror if you need to log them centrally.