selectorFamily
Similarly to atomFamily, the use case for selectorFamily is mapping values to Stan primitives. It returns a memoized function that produces selectors based on a given parameter (which must be serializable).
Among other things, it can be especially useful for:
- Building selectors with context-dependent behavior
- Memoizing selectors that perform expensive computations, make API calls, and so on
const selectorFamily: <T, P extends SerializableParam>(
selectorFamilyFn: SelectorFamilyFn<T, P>,
options?: SelectorFamilyOptions<P>,
) => (param: P) => Scoped<ReadonlyState<T>>;
selectorFamilyFn- A function with the following signature:<T, P extends SerializableParam>(param: P) => SelectorFn<T>. It takes one serializable parameter and returns a selector function (seeselectorfor details).options?- Selector family configuration:-
tag?- A string identifier (seeselectorfor details). Alternatively, it can be a function with the following signature:<P extends SerializableParam>(param: P) => string. The function form is useful when the tag should depend on the parameter. -
areValuesEqual?- A function used to determine whether two consecutive selector values are equal (seeselectorfor details). -
cachePolicy?- Configures the behavior of the selector family cache. Possible values:{ type: 'keep-all' }(default) - A selector instance will be cached for every parameter.{ type: 'most-recent' }- Only the last parameter's selector instance will be cached.{ type: 'lru'; maxSize: number }- Only the most recentmaxSizeselector instances will be cached.
Every variant additionally accepts an optional
ttl: number(in milliseconds). The timer starts when an entry is added to the cache, and the expired entry is evicted lazily on the next access. While an entry has active subscribers (i.e. it's mounted), eviction is deferred - the entry remains stable until it is unreferenced again.
-
Stan does not rely on referential equality for selectorFamily parameters, so there's no need to maintain stable references. Cache keys are computed by serializing (stable stringification) the parameter values - hence the serializability requirement.
Example
Get user by id:
const userById = selectorFamily<Promise<User>, string>(
userId => () => getUser(userId),
);
Same, but abort the pending request:
const userById = selectorFamily<Promise<User>, string>(
userId =>
({ signal }) =>
getUser(userId, { signal }),
);
Same, but let's cache only up to 5 requests:
const userById = selectorFamily<Promise<User>, string>(
userId =>
({ signal }) =>
getUser(userId, { signal }),
{
cachePolicy: {
type: 'lru',
maxSize: 5,
},
},
);
Same, but also refresh each cached request after a minute:
const userById = selectorFamily<Promise<User>, string>(
userId =>
({ signal }) =>
getUser(userId, { signal }),
{
cachePolicy: {
type: 'lru',
maxSize: 5,
ttl: 60_000,
},
},
);
Map the result to a different value:
const userNameById = selectorFamily<Promise<string>, string>(
userId =>
async ({ get }) => {
const { name } = await get(userById(userId));
return name;
},
);
Render:
- React
- Vue
const MyComponent: FC<{ userId: string }> = ({ userId }) => {
const result = useStanValueAsync(userNameById(userId));
switch (result.type) {
case 'loading':
return <p>Loading…</p>;
case 'error':
return <p>Nope</p>;
case 'ready':
return <p>Name: {result.value}</p>;
}
};
<script setup lang="ts">
import { computed } from 'vue';
const props = defineProps<{ userId: string }>();
const result = useStanValueAsync(computed(() => userNameById(props.userId)));
</script>
<template>
<p v-if="result.type === 'loading'">Loading…</p>
<p v-else-if="result.type === 'error'">Nope</p>
<p v-else>Name: {{ result.value }}</p>
</template>