| name | mongez-atom-defining-atoms |
| description | How to create atoms with `createAtom` and `atomCollection` — options, actions, typing, object helpers, and the built-in mutation API.
TRIGGER when: code imports or calls `createAtom` or `atomCollection`, uses `Atom<V>` / `AtomOptions` generics, or invokes `merge`, `change`, `silentChange`, `silentUpdate`, `beforeUpdate`, `watch`, `onChange`, `onReset`, `clone`, `destroy`; user asks "how do I create an atom", "how do I type an atom", "how do I add actions/methods to my atom", or "how do I subscribe to atom changes"; `import { createAtom, atomCollection } from "@mongez/atom"`.
SKIP: deep reference of every base method (use `mongez-atom-atoms` for full surface); array verb specifics (use `mongez-atom-collections`); computed atoms (use `mongez-atom-derived` / `mongez-atom-derived-atoms`); SSR stores (use `mongez-atom-atom-store` / `mongez-atom-stores`); persistence (use `mongez-atom-persist` / `mongez-atom-persistence`); React hooks (live in `@mongez/react-atom`).
|
Defining Atoms
When to use
Load this skill when the user is:
- Creating a new atom with
createAtom or atomCollection
- Adding typed actions to an atom
- Using
merge, change, watch, silentUpdate, or beforeUpdate
- Subscribing to changes with
onChange or onReset
- Working with
reset, destroy, or clone
Install
yarn add @mongez/atom
Basic atom
import { createAtom } from "@mongez/atom";
const counter = createAtom({
key: "counter",
default: 0,
});
counter.value;
counter.update(5);
counter.update(prev => prev + 1);
counter.silentUpdate(0);
counter.reset();
counter.silentReset();
Typed atoms
Atom<V> is a generic. The default value drives inference; annotate when the inferred type is too narrow:
type Theme = "light" | "dark";
const themeAtom = createAtom({
key: "ui.theme",
default: "light" as Theme,
});
themeAtom.update("dark");
themeAtom.update("blue");
For objects:
type User = { name: string; age: number };
const userAtom = createAtom({
key: "user",
default: { name: "Anon", age: 0 } satisfies User,
});
Object-atom helpers (only on Atom)
When Value is an object or array, these extra methods exist. Calling them on a primitive atom (Atom<boolean>, Atom<number>) is a compile error.
const user = createAtom({
key: "user",
default: { name: "Anon", role: "viewer" as "viewer" | "admin" },
});
user.merge({ role: "admin" });
user.change("name", "Alice");
user.silentChange("name", "Bob");
user.get("name");
const sub = user.watch("role", (next, prev) => {
console.log("role changed from", prev, "to", next);
});
sub.unsubscribe();
Actions (verbs on the atom)
Actions turn an atom into a command object. this inside each action is the atom instance.
import { createAtom, type Atom } from "@mongez/atom";
const sidebar = createAtom({
key: "ui.sidebar",
default: false,
actions: {
open() { this.update(true); },
close() { this.update(false); },
toggle() { this.update(!this.value); },
},
});
sidebar.open();
sidebar.toggle();
sidebar.value;
TypeScript infers action types from the actions object — no extra type annotations needed for simple cases.
Actions with arguments
type CartItem = { id: string; price: number; qty: number };
const cart = createAtom({
key: "cart",
default: [] as CartItem[],
actions: {
addItem(item: CartItem) {
this.update([...this.value, item]);
},
setQty(id: string, qty: number) {
this.update(this.value.map(i => i.id === id ? { ...i, qty } : i));
},
removeItem(id: string) {
this.update(this.value.filter(i => i.id !== id));
},
},
});
cart.addItem({ id: "a", price: 10, qty: 1 });
cart.setQty("a", 3);
cart.removeItem("a");
Getter actions (computed properties)
Use ES5 property descriptors (get keyword inside the actions object). These are installed as real getters on the atom instance:
const cart = createAtom({
key: "cart2",
default: [] as CartItem[],
actions: {
get total() {
return this.value.reduce((sum, i) => sum + i.price * i.qty, 0);
},
},
});
cart.total;
atomCollection — arrays with built-in mutation verbs
Use atomCollection instead of createAtom when the value is an array. It pre-installs all common mutation helpers:
import { atomCollection } from "@mongez/atom";
type Todo = { id: number; text: string; done: boolean };
const todos = atomCollection<Todo>({ key: "todos" });
todos.push({ id: 1, text: "Buy bread", done: false });
todos.unshift({ id: 0, text: "Wake up", done: true });
todos.pop();
todos.shift();
todos.remove(t => t.done);
todos.remove(2);
todos.removeItem(someRef);
todos.removeAll(someRef);
todos.replace(0, { id: 0, text: "Wake up EARLY", done: true });
todos.map(t => ({ ...t, done: true }));
todos.get(0);
todos.get(t => t.id === 1);
todos.index(t => t.id === 1);
todos.forEach(t => console.log(t));
todos.length;
todos.value;
You can still add custom actions alongside the built-in ones:
const todos2 = atomCollection<Todo>({
key: "todos2",
actions: {
markAllDone() {
this.update(this.value.map(t => ({ ...t, done: true })));
},
},
});
Subscriptions
const counter = createAtom({ key: "c", default: 0 });
const sub = counter.onChange((next, prev, atom) => {
console.log(next, prev);
});
sub.unsubscribe();
counter.onReset(atom => console.log("reset"));
counter.onDestroy(atom => console.log("destroyed"));
beforeUpdate hook
Intercept and transform (or reject) every incoming value before it is stored:
const boundedAngle = createAtom({
key: "angle",
default: 0,
beforeUpdate(next, prev, atom) {
return ((next % 360) + 360) % 360;
},
});
boundedAngle.update(450);
boundedAngle.value;
Return undefined (or nothing) to let the original value through unchanged.
Clone and destroy
const a = createAtom({ key: "original", default: 42 });
const b = a.clone();
const c = a.clone({ register: false });
a.destroy();
Key pitfalls
default must be a complete value — no Partial<V> defaults. AtomOptions.default: V, not Partial<V>.
update() short-circuits when newValue === currentValue (strict reference equality). Always pass a new object/array reference for object atoms. merge() and change() do this for you.
silentUpdate still runs beforeUpdate — it only suppresses the update event emission.
reset() triggers both an update event (with the default value) and a reset event. silentReset() only triggers the reset event.
- Actions are bound to the atom with
.bind(atom) at creation time. Arrow functions in actions also work but this will be undefined — use regular functions so this resolves correctly.
atomCollection.map() mutates and updates the atom. It is not a read-only .map(). Use todos.value.map(...) if you only want to read.