Skip to content

feat: add memo utility#420

Open
MathiasWP wants to merge 2 commits into
svecosystem:mainfrom
MathiasWP:derived-watch
Open

feat: add memo utility#420
MathiasWP wants to merge 2 commits into
svecosystem:mainfrom
MathiasWP:derived-watch

Conversation

@MathiasWP
Copy link
Copy Markdown

@MathiasWP MathiasWP commented May 7, 2026

Summary

Adds a new memo utility — the $derived analog of watch.

$derived automatically tracks every signal read inside its expression. That's usually what you want, but sometimes the compute body needs to read state that shouldn't trigger recomputation. Today the only escape hatch is wrapping every unrelated read in untrack, which is verbose and easy to get wrong.

memo flips the model and mirrors watch's API split:

  • Arg 1: a getter (or array of getters) that declares what should trigger reactivity. Its return value is only used to register dependencies.
  • Arg 2: a zero-argument compute function that produces the value. Reads inside it are not tracked.
let count = $state(2);
const doubled = memo(() => count, () => count * 2);
doubled.current; // 4

let a = $state(1);
let b = $state(2);
const sum = memo([() => a, () => b], () => a + b);

// only `count` triggers recomputation; `multiplier` is read but not tracked
let multiplier = $state(2);
const value = memo(() => count, () => count * multiplier);

Implementation

  • packages/runed/src/lib/utilities/memo/memo.svelte.tsmemo function. Touches the source getter(s) to register them as dependencies, then runs the compute body inside untrack so nothing inside it gets tracked. Internally constructs a small class that holds a $derived.by and exposes the value via .current (the standard runed pattern, since runes are compiler macros and can't be returned as bare reactive values from a function).
  • packages/runed/src/lib/utilities/memo/memo.test.svelte.ts — 4 tests:
    • recomputes when a tracked source changes
    • does not recompute when an untracked source read inside the compute body changes
    • supports an array of sources
    • only the listed sources trigger recomputation (cross-check that the latest value of an un-tracked read is still picked up on the next recomputation)
  • packages/runed/src/lib/utilities/memo/index.ts + packages/runed/src/lib/utilities/index.ts — re-exports.
  • sites/docs/src/content/utilities/memo.md — docs page modeled directly on watch.md for consistency (same framing paragraph structure, same example progression: single source → array → untracked-read example).
  • .changeset/add-memo-utility.mdminor bump.

Test plan

  • pnpm exec vitest --run src/lib/utilities/memo — 4/4 passing
  • pnpm check — 0 errors, 0 warnings
  • Maintainer review on naming (memo vs alternatives) and on the choice to expose via .current rather than a different accessor

🤖 Generated with Claude Code

`memo` is to `$derived` what `watch` is to `$effect`: it accepts an
explicit list of source getters and only recomputes when one of them
changes. Reads inside the compute body are not tracked.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 7, 2026

🦋 Changeset detected

Latest commit: 2ea24d1

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
runed Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

The first argument now exists solely to register dependencies; the
compute callback takes no arguments and just produces the value. This
matches the shape of `watch` more directly: arg 1 = "what triggers
reactivity", arg 2 = "what to do".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@AndersRobstad
Copy link
Copy Markdown

Very useful! I often end up inlining such with derived and watch

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants