Collection of custom memo primitives. They extend createMemo functionality while keeping the usage similar.
| Stage | Category | Version | Last Updated | Demo |
|---|---|---|---|---|
| 3 | Reactivity | 2.0.0-next.2 (next) | Aug 13, 2026 | Demo → |
npm i @solid-primitives/memo@nextCollection of custom createMemo primitives. They extend it's functionality while keeping the usage similar.
createLatest- A combined memo of a list of sources, returns the value of last updated one.createLatestMany- A combined memo of a list of sources, returns the value of all last updated ones.createWritableMemo- Solid'screateMemowhich value can be overwritten by a setter.createLazyMemo- Lazily evaluated memo. Will run the calculation only if is being listened to.createPureReaction- AcreateReactionthat runs before render (non-batching).createMemoCache- Custom, lazily-evaluated, memo, with caching based on keys.createReducer- Primitive for updating signal in a predictable way.
createLatest
A combined memo of multiple sources, last updated source will be the value of the returned signal.
How to use it
createLatest takes two arguments:
sources- list of reactive calculations/signals/memosoptions- memo options
And returns a signal with value of the last change
import { createLatest } from "@solid-primitives/memo";
const [count, setCount] = createSignal(1);const [text, setText] = createSignal("hello");
const lastUpdated = createLatest([count, text]);
lastUpdated(); // => "hello"setCount(4);lastUpdated(); // => 4createLatestMany
A combined memo of multiple sources, returns the values of sources updated in the last tick.
How to use it
createLatestMany takes two arguments:
sources- list of reactive calculations/signals/memosoptions- memo options
And returns a signal with value of the last updated sources
import { createLatestMany } from "@solid-primitives/memo";
const [count, setCount] = createSignal(1);const [text, setText] = createSignal("hello");
const lastUpdated = createLatestMany([count, text]);
lastUpdated(); // => [1, "hello"]setCount(4);lastUpdated(); // => [4]createWritableMemo
Solid's createMemo which value can be overwritten by a setter.
How to use it
createWritableMemo takes the same arguments as Solid's createMemo:
calc- callback that calculates the valuevalue- initial value (for calcultion)options- give a name to the reactive computation, or changeequalsmethod.
And returns a signal with value of the last change, set by a setter or a memo calculation.
import { createWritableMemo } from "@solid-primitives/memo";
const [count, setCount] = createSignal(1);const [result, setResult] = createWritableMemo(() => count() * 2);result(); // => 2setResult(5); // overwrites calculation resultresult(); // => 5Props derived signals
Using createWritableMemo will let you create a signal from a prop, that can be overwritten by a setter. This is useful for creating a signal from a prop, that can be used in a child component, but also be overwritten by a parent.
const Child: Component<{ selectedId: string }> = props => { const [selectedId, setSelectedId] = createWritableMemo(() => props.selectedId);
return ( <div> <div>Selected id: {selectedId()}</div> <button onClick={() => setSelectedId("1")}>Select 1</button> <button onClick={() => setSelectedId("2")}>Select 2</button> </div> );};createLazyMemo
Lazily evaluated createMemo. Will run the calculation only if is being listened to.
It may be useful for memos that aren't being listened to all the time, to reduce performance cost of wastefull computations.
How to use it
It's usage is almost the same as Solid's createMemo. Similarly it should be placed inside a reactive root — component or createRoot.
import { createLazyMemo } from "@solid-primitives/memo";
// use like a createMemoconst double = createLazyMemo(() => count() * 2);double(); // T: numberSet the initial value, or type of the previous value in calculation function will be T | undefined.
// set the initial valueconst memo = createLazyMemo(prev => count() + prev, 123);memo(); // T: numberSee the tests for better usage reference.
No equals
createLazyMemo always uses equals: false internally. A lazy memo cannot eagerly evaluate to check equality — it needs to always notify its observers so they can pull and evaluate the new value.
Auto-disposal and recomputation
In Solid 2.0, createLazyMemo is backed by a native createMemo({ lazy: true }). When all observers disconnect the memo auto-disposes, and the next observer that reads it triggers a fresh computation from scratch. This means the previous value is not retained across idle periods — prev will be undefined again after a full unsubscribe/resubscribe cycle.
You may not need a lazy memo
There are very few actual good applications of a lazy memo, that couldn't be solved with other means — like improving the data architecture. For example, you can always only create memos in places that you intend to use it in, instead of declaring it prematurely.
// instead of memo, distribute only a calculation functionconst getDouble = (n: number) => n * 2;// and only declare memo where you want to use itconst double = createMemo(() => getDouble(count()));createDebouncedMemo
Removed in v2. createDebouncedMemo was deprecated in v1 and has been removed. Use createScheduled from @solid-primitives/schedule once that package is updated for Solid 2.0.
createThrottledMemo
Removed in v2. createThrottledMemo was deprecated in v1 and has been removed. Use createScheduled from @solid-primitives/schedule once that package is updated for Solid 2.0.
createAsyncMemo
Removed in v2. createAsyncMemo was deprecated in v1 (pointing to createResource) and has been removed. createResource does not exist in Solid 2.0. Async data fetching in Solid 2.0 can be handled with createProjection for store-based async state, or with a signal + createEffect for manual async management.
createPureReaction
Solid's createReaction that is based on pure computation (runs before render, and is non-batching)
How to use it
It's usage exactly matches the original. The only difference is in when the callback is being executed, the normal createReaction runs it after render, similar to how effects work, while the createPureReaction is more like createComputed.
import { createPureReaction } from "@solid-primitives/memo"
const [count, setCount] = createSignal(0);const track = createPureReaction(() => {...});track(count);setCount(1); // triggers callback
// sources need to be re-tracked every timesetCount(2); // doesn't trigger callbackDefinition
function createPureReaction(onInvalidate: Fn, options?: EffectOptions): (tracking: Fn) => void;createMemoCache
Custom, lazily-evaluated, cached memo. The caching is based on a key, it has to be declared up-front as a reactive source, or passed to the signal access function.
how to use it
It takes params:
keya reactive source, that will serve as cache key (later value access for the same key will be taken from cache instead of recalculated)calccalculation function returning value to cache. the function is tracking - will recalculate when the accessed signals change.optionsset maximum size of the cache, or memo options.
Returns a signal access function.
Import
import { createMemoCache } from "@solid-primitives/memo";Setting the key up-front as a reactive source
const [count, setCount] = createSignal(1);const double = createMemoCache(count, n => n * 2);// access value:double();Provide the key by passing it to the access function
let's accessing different keys in different places
const [count, setCount] = createSignal(1);const double = createMemoCache((n: number) => n * 2);// access value with key:double(count());Calculation function is reactive
will recalculate when the accessed signals change.
// changing number creates new entry in cacheconst [number, setNumber] = createSignal(1);// changing divisor will force cache to be recalculatedconst [divisor, setDivisor] = createSignal(1);
// calculation subscribes to divisor signalconst result = createMemoCache(number, n / divisor());Definition
function createMemoCache<Key, Value>( key: Accessor<Key>, calc: CacheCalculation<Key, Value>, options?: CacheOptions<Value>,): Accessor<Value>;function createMemoCache<Key, Value>( calc: CacheCalculation<Key, Value>, options?: CacheOptions<Value>,): CacheKeyAccessor<Key, Value>;
type CacheCalculation<Key, Value> = (key: Key, prev: Value | undefined) => Value;type CacheKeyAccessor<Key, Value> = (key: Key) => Value;type CacheOptions<Value> = MemoOptions<Value> & { size?: number };createReducer
Primitive for updating signal in a predictable way. SolidJS equivalent of React's useReducer.
When to use it
createReducer is useful for:
- DRY the code of the
sets of a signal - Ensure the signal is always in a valid state
- Make it easier to understand for what a signal is used
How to use it
function createReducer<T, ActionData extends any[]>( dispatcher: (state: T, ...args: ActionData) => T, initialValue: T, options?: SignalOptions<T>,): [accessor: Accessor<T>, dispatch: (...args: ActionData) => void];dispatcher is the reducer, it's 1st parameter always is the current state of the reducer and it returns the new state of the reducer.
accessor can be used as you use a normal signal: accessor(). It contains the state of the reducer.
dispatch is the action of the reducer, it is a sort of setSignal that does NOT receive the new state, but instructions to create it from the current state.
For example:
import { createReducer } from "@solid-primitives/memo";
function Counter() { const [count, double] = createReducer(c => c * 2, 1);
return <button onClick={double}>{count()}</button>;}The reducer also can receive other arguments:
import { createReducer } from "@solid-primitives/memo";
const dispatcher = (c: number, type: "double" | "increment") => { if (type == "double") { return c * 2; } else { return c + 1; }};
function Counter() { const [count, handleClick] = createReducer(dispatcher, 1);
return ( <div> <span>{count()}</span> <button onClick={() => handleClick("double")}>Double</button> <button onClick={() => handleClick("increment")}>Increment</button> </div> );}React allows a 3rd argument:
const fib = (n: number) => (n < 2 ? n : fib(n - 1) + fib(n - 2));const nextFib = (n: number) => Math.round((n * (1 + sqrt(5))) / 2);
const [fibonacci, nextFibonacci] = useReducer(nextFib, 1, fib);You need to convert that to the following format:
const [fibonacci, nextFibonacci] = createReducer(nextFib, fib(1));Changelog
See CHANGELOG.md