Introduction: Memos

Memos (Computed Values)

Memos are derived values that cache their result and only recompute when dependencies change.

Creating Memos

import { createSignal, createMemo } from '@luna_ui/luna';

const [count, setCount] = createSignal(2);

// Computed value that depends on count
const doubled = createMemo(() => count() * 2);

console.log(doubled());  // 4

setCount(5);
console.log(doubled());  // 10

Why Use Memos?

1. Avoid Redundant Computation

Without memo, expensive calculations run every time:

// Bad: filterItems runs on every render
function ItemList() {
  const [items, setItems] = createSignal([...]);
  const [filter, setFilter] = createSignal("");

  return (
    <ul>
      {items().filter(i => i.name.includes(filter())).map(item => (
        <li>{item.name}</li>
      ))}
    </ul>
  );
}

// Good: filterItems only runs when items or filter change
function ItemList() {
  const [items, setItems] = createSignal([...]);
  const [filter, setFilter] = createSignal("");

  const filteredItems = createMemo(() =>
    items().filter(i => i.name.includes(filter()))
  );

  return (
    <ul>
      {filteredItems().map(item => (
        <li>{item.name}</li>
      ))}
    </ul>
  );
}

2. Share Computed Values

const [firstName, setFirstName] = createSignal("John");
const [lastName, setLastName] = createSignal("Doe");

// Shared computed value
const fullName = createMemo(() => `${firstName()} ${lastName()}`);

// Used in multiple places - computed once
<h1>Welcome, {fullName()}</h1>
<p>Logged in as {fullName()}</p>

Memos Are Cached

Memos only recompute when their dependencies change:

const [count, setCount] = createSignal(0);
const [other, setOther] = createSignal(0);

const expensive = createMemo(() => {
  console.log("Computing...");
  return count() * 2;
});

expensive();  // Logs "Computing...", returns 0
expensive();  // No log (cached), returns 0

setOther(5);  // Does NOT trigger recomputation
expensive();  // No log (cached), returns 0

setCount(1);  // Triggers recomputation
expensive();  // Logs "Computing...", returns 2

Chained Memos

Memos can depend on other memos:

const [price, setPrice] = createSignal(100);
const [quantity, setQuantity] = createSignal(2);
const [taxRate, setTaxRate] = createSignal(0.1);

const subtotal = createMemo(() => price() * quantity());
const tax = createMemo(() => subtotal() * taxRate());
const total = createMemo(() => subtotal() + tax());

console.log(total());  // 220

setQuantity(3);
console.log(total());  // 330 (only affected memos recompute)

Memo vs Effect

AspectMemoEffect
Returns valueYesNo
Caches resultYesNo
ForDerived dataSide effects
LazyYes (computed on read)No (runs immediately)
// Use Memo for computed values
const doubled = createMemo(() => count() * 2);

// Use Effect for side effects
createEffect(() => {
  document.title = `Count: ${count()}`;
});

Common Patterns

Filtered/Sorted Lists

const [items, setItems] = createSignal([...]);
const [sortBy, setSortBy] = createSignal("name");
const [filterText, setFilterText] = createSignal("");

const displayItems = createMemo(() => {
  return items()
    .filter(i => i.name.includes(filterText()))
    .sort((a, b) => a[sortBy()].localeCompare(b[sortBy()]));
});

Validation

const [email, setEmail] = createSignal("");
const [password, setPassword] = createSignal("");

const isValid = createMemo(() => {
  const emailOk = email().includes("@");
  const passwordOk = password().length >= 8;
  return emailOk && passwordOk;
});

<button disabled={!isValid()}>Submit</button>

Conditional Logic

const [user, setUser] = createSignal(null);

const greeting = createMemo(() => {
  const u = user();
  if (!u) return "Please log in";
  if (u.isAdmin) return `Welcome, Admin ${u.name}`;
  return `Hello, ${u.name}`;
});

Try It

Create a shopping cart with:

  1. An items signal (array of {name, price, quantity})

  2. A subtotal memo

  3. A tax memo (10%)

  4. A total memo

Solution
const [items, setItems] = createSignal([
  { name: "Apple", price: 1.00, quantity: 3 },
  { name: "Banana", price: 0.50, quantity: 5 },
]);

const subtotal = createMemo(() =>
  items().reduce((sum, item) => sum + item.price * item.quantity, 0)
);

const tax = createMemo(() => subtotal() * 0.1);

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

// Display
<p>Subtotal: ${subtotal().toFixed(2)}</p>
<p>Tax: ${tax().toFixed(2)}</p>
<p>Total: ${total().toFixed(2)}</p>

Next

Learn about Batch Updates โ†’