selectorFamily

Signature
selectorFamily<K, T>(factory: (key: K) => (get: GetFn, options: SelectorGetOptions) => T): SelectorFamily<K, T>

valdres Create a keyed collection of derived selectors

Creates a family of selectors keyed by a parameter. Each selector derives its value from atoms or other selectors based on the key.

The family object is a factory, not readable or subscribable state. Read or subscribe to a member such as userDisplayNameSelector("user-1"). Unlike an atomFamily, creating a selector-family member does not add store-local state or membership to enumerate.

Selector options apply to every family member. In particular, use { mutable: true } when results contain mutable built-ins or host objects; see the selector immutability contract.

Member identity and lifetime

A member's identity is stable while live: repeated calls with the same key return the same selector while a caller or live store strongly retains it, such as through a subscription, dependency edge, or an enumerable store's cold cache. A default non-enumerable store uses weak keys for cold entries, so a cold read there does not by itself keep a member alive. The family cache is weak; after a member becomes unreachable, garbage collection may reclaim it and a later call can create a fresh identity.

release(...args) explicitly evicts the cached lookup early. Existing callers and stores may continue using the released selector, while a later family call can create a new member for those arguments.

Each member getter also receives SelectorGetOptions. Its signal is aborted when that evaluation is superseded or disposed, and storeId identifies the store evaluating the member. Existing getters that only accept get remain valid.

const userSelector = selectorFamily(
    userId =>
        (get, { signal, storeId }) =>
            fetch(`/api/users/${userId}?store=${storeId}`, { signal }).then(
                res => res.json(),
            ),
)

Usage

import { selectorFamily } from "valdres"
import { userAtom } from "./userAtom"

const userDisplayNameSelector = selectorFamily(id => get => {
    const { firstName, lastName } = get(userAtom(id))
    return `${firstName} ${lastName}`
})

store.get(userDisplayNameSelector("user-1")) // "John Doe"

Custom key identity

Selector families use the same deterministic key codec as atomFamily: types and argument count are distinct, while plain Object keys and Map/Set contents are canonicalized independent of insertion order. Unsupported values (including Symbols, functions, Promises, class instances, and cycles) throw a TypeError.

Provide keyOf when the original argument is unsupported or when only part of it should determine selector identity:

const userRevision = selectorFamily(
    user => get => `${get(userAtom(user.id)).name}@${user.revision}`,
    { keyOf: user => [user.id, user.revision] },
)

keyOf is also the lever for read-heavy code. A single string or number argument is looked up directly, but an object argument has to be canonicalized on every call — family({ id }) in a loop re-encodes that object each time, while family(id) does not. If a family with structured arguments sits on a hot path, give it a keyOf that projects the identifying part:

// re-encodes { id, revision } on every call
const slow = selectorFamily(user => get => get(userAtom(user.id)).name)

// encodes one string instead
const fast = selectorFamily(user => get => get(userAtom(user.id)).name, {
    keyOf: user => user.id,
})

With async computation

const userPostsSelector = selectorFamily(userId => get =>
    fetch(`/api/users/${userId}/posts`).then(res => res.json()),
)

As with selector(), the member getter itself must be synchronous. It may return a Promise, but a native async function is rejected because Valdres unwraps settled selector values and therefore cannot preserve an async function's always-Promise return contract.

In React

import { useValue } from "valdres-react"

function UserName({ userId }) {
    const displayName = useValue(userDisplayNameSelector(userId))
    return <span>{displayName}</span>
}

See also

  • selector — create a single selector
  • atomFamily — create a family of atoms
  • index — reactively filter an atom family by a term