Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Patterns

Framework state

A value is a plain object, so a state container holds it as it stands. Replace it whole. The untouched subtrees keep their identity, so a dependency array sees no change.

const [shop, setShop] = useState(Shop({ owner, city }));
setShop(Shop.patch(shop, { owner: { email: "e@example.com" } }));

useEffect(() => showMap(shop.city), [shop.city]); // the patch did not touch `city`: no re-run

Solid reads the same with createSignal, and takes the comparison directly: createSignal(user, { equals }).

Svelte and Vue are deeply reactive by default, so ask for a shallow container. A deep one gives your code a proxy in place of the value, and patch no longer recognizes the nodes it owns, so it copies them again.

Values are frozen in development, so Vue skips them and ref behaves like shallowRef until you build for production. A write through Vue’s ref or Solid’s createStore then mutates the value instead of throwing.

framework
ReactuseState
SolidcreateSignal, not createStore
Svelte 5$state.raw, not $state
VueshallowRef, not ref

Map / Set

Use an object’s properties.

type Tags = Val<"Tags", Record<string, true>>; // a Set
type PriceTable = Val<"PriceTable", Record<string, Money>>; // a Map

Use true rather than null for a set, so if (tags[key]) is the membership test. equals ignores key order, so comparing two of them is set equality.

patch sets one entry at a time, and undefined removes it. Call the constructor to rebuild the whole table.

PriceTable.patch(table, { apple: Money({ amount: 120, currency: "JPY" }), fig: undefined });

PriceTable(
  // the value type is named because a Val carries its phantom keys in the type as well
  Object.fromEntries(Object.entries<Money>(table).filter(([, m]) => m.amount < 500)),
);

Dates

export type UnixEpochMs = Val<"UnixEpochMs", number>;

export const UnixEpochMs = Val.sealer<UnixEpochMs>().impl({
  showLocal(d) {
    return Temporal.Instant.fromEpochMilliseconds(d).toLocaleString();
  },
});