Memos
Edit this pageMemos are a type of reactive value that can be used to memoize derived state or expensive computations. They are similar to derived signals in that they are reactive values that automatically re-evaluate when their dependencies change. However, unlike derived signals, memos are optimized to execute only once for each change in their dependencies.
In Solid 2.0, createMemo can return a Promise or AsyncIterable. When a memo returns a Promise, any consumer that reads it while unresolved will suspend — wrap reads in <Loading> to handle the loading state. This replaces createResource from Solid 1.x.
const user = createMemo(() => fetchUser(params.id));
// Wrap in <Loading> to handle the suspended state<Loading fallback={<Spinner />}> <Profile user={user()} /></Loading>Memos expose a read-only reactive value (like a signal) and track changes in their dependencies (similar to an effect). This makes them useful for caching the results of expensive or frequently accessed computations. By doing this, memos minimize unnecessary work within an application by retaining the results of a computation until its dependencies change.
Using memos
A memo is created using the createMemo function.
Within this function, you can define the derived value or computations you wish to memoize.
When called, createMemo will return a getter function that reads the current value of the memo:
import { createMemo, createSignal } from "solid-js"
const [count, setCount] = createSignal(0)
const isEven = createMemo(() => count() % 2 === 0)
console.log(isEven()) // true
setCount(3)console.log(isEven()) // falseWhile memos look similar to effects, they are different in that they return a value. This value is the result of the computation or derived state that you wish to memoize.
Advantages of using memos
While you can use a derived signal to achieve similar results, memos offer distinct advantages:
- Memos are optimized to execute only once for each change in their dependencies.
- When working with expensive computations, memos can be used to cache the results so they are not recomputed unnecessarily.
- A memo will only recompute when its dependencies change, and will not trigger subsequent updates (as determined by
===or strict equality) if its dependencies change but its value remains the same. - Any signal or memo accessed within a memo's function is tracked. This means that the memo will re-evaluate automatically when these dependencies change.
Memo vs. effect
Both memos and effects are important when managing reactive computations and side effects. They, however, serve different purposes and each has their own unique behaviors.
| Memos | Effects | |
|---|---|---|
| Return value | Yes - returns a getter for the result of the computation or derived state. | Does not return a value but executes a block of code in response to changes. |
| Caches results | Yes | No |
| Behavior | Function argument should be pure without reactive side effects. | Function argument can cause side effects like UI updates or data fetches. |
| Dependency tracking. | Yes | Yes |
| Example use cases | Transforming data structures, computing aggregated values, derived state, or other expensive computations. | UI updates, network requests, or external integrations. |
Best practices
Pure functions
When working with memos, it is recommended that you leave them "pure".
import { createSignal, createMemo } from "solid-js"
const [count, setCount] = createSignal(0)const isEven = createMemo(() => count() % 2 === 0) // example of a pure functionA pure function is one that does not cause any side effects. This means that the function's output should solely depend on its inputs.
When you introduce side effects into a memo, it can complicate the reactivity chain. This can lead to unexpected behavior, such as infinite loops, that lead your application to crash.
In Solid 2.0, writing to a signal inside any reactive scope (including memos) produces a dev warning. This makes the "keep memos pure" guidance a runtime-enforced rule.
import { createSignal, createMemo } from "solid-js"
const [count, setCount] = createSignal(0)const [message, setMessage] = createSignal("")
const badMemo = createMemo(() => { if (count() > 10) { setMessage("Count is too high!") // ❌ warns in 2.0: writing a signal inside a memo } return count() % 2 === 0})This can be avoided by using a createEffect to handle the side effects instead:
import { createSignal, createMemo, createEffect } from "solid-js"
const [count, setCount] = createSignal(0)const [message, setMessage] = createSignal("")
const isEven = createMemo(() => count() % 2 === 0)
createEffect( () => count(), (value) => { if (value > 10) { setMessage("Count is too high!") } })Here, the createEffect will handle the side effects, while the isEven memo will remain pure.
Keep logic in memos
Memos are optimized to execute only once for each change in their dependencies. This means that you can remove unnecessary effects that are triggered by a memo's dependencies.
When working with derived state, memos are the recommended approach over effects. Keeping the logic in a memo prevents unnecessary re-renders that can occur when using an effect. Similarly, effects are better suited to handle side effects, such as DOM updates, rather than derived state. This separation of concerns can help keep your code clean and easy to understand.
// memo - preferred for derived state; only recalculates when `count` changesconst message = createMemo(() => { if (count() > 10) { return "Count is too high!" } else { return "" }})
// effect - use only for actual side effects (DOM, network, etc.)createEffect( () => count(), (value) => { document.title = `Count: ${value}`; })