Agent Skill
2/7/2026

signalize-memos

Computed/derived signals for @spearwolf/signalize: createMemo for cached computations that automatically update when dependencies change. Use when creating derived values that depend on other signals.

S
spearwolf
32GitHub Stars
1Views
npx skills add spearwolf/signalize

SKILL.md

Namesignalize-memos
DescriptionComputed/derived signals for @spearwolf/signalize: createMemo for cached computations that automatically update when dependencies change. Use when creating derived values that depend on other signals.

name: signalize-memos description: 'Computed/derived signals for @spearwolf/signalize: createMemo for cached computations that automatically update when dependencies change. Use when creating derived values that depend on other signals.'

Signalize Memos

Overview

Memos are computed signals - cached values that automatically recalculate when their dependencies change. They combine the reactivity of effects with the read interface of signals.

import {createMemo, createSignal} from '@spearwolf/signalize';

const count = createSignal(0);
const doubled = createMemo(() => count.get() * 2);

doubled(); // 0
count.set(5);
doubled(); // 10 (automatically updated)

Quick Start

const firstName = createSignal('John');
const lastName = createSignal('Doe');

const fullName = createMemo(() => {
  return `${firstName.get()} ${lastName.get()}`;
});

fullName(); // 'John Doe'

firstName.set('Jane');
fullName(); // 'Jane Doe'

Lazy vs Non-Lazy Memos

Non-Lazy (Default)

A non-lazy memo acts as a computed signal: it recomputes immediately when dependencies change — even before anyone reads it. This means effects and other memos that depend on it are automatically notified, just like with a regular signal.

let computeCount = 0;

const doubled = createMemo(() => {
  computeCount++;
  return count.get() * 2;
});

// computeCount = 1 (computed on creation)

count.set(5);
// computeCount = 2 (recomputed immediately)

doubled(); // Returns cached value, no recompute
doubled(); // Still cached
// computeCount = 2

Key behavior: Because the memo eagerly updates its value, effects that read it will re-run when the value changes:

const count = createSignal(1);
const doubled = createMemo(() => count.get() * 2);

createEffect(() => {
  console.log('Doubled:', doubled());
});
// => "Doubled: 2"

count.set(5);
// memo recalculates (now 10), then the effect re-runs
// => "Doubled: 10"

Use when:

  • The memo is a dependency of effects or other memos
  • You need it to behave like a computed signal in a reactive chain
  • The value should always be up-to-date

Lazy

A lazy memo does not react to dependency changes on its own. It defers recomputation until the memo is actually read. The recalculation happens at the latest possible moment.

Because it does not eagerly update, effects that depend on a lazy memo will not automatically re-run when the memo's source dependencies change.

let computeCount = 0;

const doubled = createMemo(
  () => {
    computeCount++;
    return count.get() * 2;
  },
  {lazy: true},
);

// computeCount = 0 (not computed yet!)

doubled();
// computeCount = 1 (computed on first read)

count.set(5);
// computeCount = 1 (NOT recomputed yet)

doubled();
// computeCount = 2 (recomputed on read)

Use when:

  • The computation is expensive and the memo might not be read after every change
  • The memo is consumed on-demand rather than observed by effects
  • Dependencies change frequently but the value is consumed infrequently

createMemo Options

createMemo(factory, options?)
OptionTypeDefaultDescription
lazybooleanfalseDefer computation until read
attachobject | SignalGroup-Attach to group for lifecycle
namestring | symbol-Named signal in group
prioritynumber1000Execution priority (higher = first)

attach and name

const group = SignalGroup.findOrCreate(this);

const fullName = createMemo(() => `${firstName.get()} ${lastName.get()}`, {
  attach: group,
  name: 'fullName',
});

// Later: access by name
group.signal('fullName');

priority

Memos have default priority 1000, higher than effects (default 0). This ensures memos compute before effects that depend on them.

// Custom priority
const critical = createMemo(compute, {priority: 2000}); // Runs first
const normal = createMemo(compute); // Priority 1000
const deferred = createMemo(compute, {priority: 500}); // Runs later

Caching Behavior

Memos cache their result and only recompute when dependencies actually change:

let computeCount = 0;

const expensive = createMemo(() => {
  computeCount++;
  return heavyCalculation(input.get());
});

expensive(); // computeCount = 1
expensive(); // computeCount = 1 (cached)
expensive(); // computeCount = 1 (still cached)

input.set(newValue);
expensive(); // computeCount = 2 (recomputed)

Memo vs Effect

AspectMemoEffect
Returns valueYESNO
Caches resultYESNO
Can be readYES (like signal)NO
Side effectsAvoidExpected
Use forDerived dataActions/DOM updates
// MEMO: Derived data
const total = createMemo(() =>
  items.get().reduce((sum, i) => sum + i.price, 0),
);

// EFFECT: Side effect
createEffect(() => {
  document.title = `Total: ${total()}`;
});

Chained Memos

Memos can depend on other memos:

const items = createSignal([{price: 10}, {price: 20}]);
const taxRate = createSignal(0.1);

const subtotal = createMemo(() =>
  items.get().reduce((sum, i) => sum + i.price, 0),
);

const tax = createMemo(() => subtotal() * taxRate.get());

const total = createMemo(() => subtotal() + tax());

total(); // 33 (30 + 3)

Cleanup

Memos are destroyed like signals:

const memo = createMemo(() => compute());

// Using destroySignal
destroySignal(memo);

// Or via SignalGroup
const group = SignalGroup.findOrCreate(obj);
createMemo(compute, {attach: group});
group.clear(); // Destroys all attached memos

Common Patterns

Filtered List

const items = createSignal([1, 2, 3, 4, 5]);
const filter = createSignal((n: number) => n > 2);

const filtered = createMemo(() => items.get().filter(filter.get()));

Sorted Data

const data = createSignal([{name: 'B'}, {name: 'A'}]);
const sortKey = createSignal('name');

const sorted = createMemo(() =>
  [...data.get()].sort((a, b) =>
    a[sortKey.get()].localeCompare(b[sortKey.get()]),
  ),
);

Expensive Computation with Lazy

const searchQuery = createSignal('');
const allItems = createSignal([
  /* large dataset */
]);

// Only compute when actually needed
const searchResults = createMemo(
  () => {
    const query = searchQuery.get().toLowerCase();
    return allItems
      .get()
      .filter((item) => item.name.toLowerCase().includes(query));
  },
  {lazy: true},
);

Pitfalls to Avoid

1. Side effects in memos

// BAD - memos should be pure
const bad = createMemo(() => {
  console.log('Computing...'); // Side effect!
  return value.get() * 2;
});

// GOOD - use effect for side effects
const good = createMemo(() => value.get() * 2);
createEffect(() => {
  console.log('Value doubled:', good());
});

2. Expecting lazy memo to be current

const memo = createMemo(compute, {lazy: true});

signal.set(newValue);
// memo() might return OLD value until you call it!

3. Forgetting cleanup

// Always destroy memos or use SignalGroup
const memo = createMemo(compute);
// ... later ...
destroySignal(memo); // Don't forget!

See Also

Skills Info
Original Name:signalize-memosAuthor:spearwolf