Skip to content

pick()

Fine-grained value tracking for computed values.

Unlike direct property access which tracks the property itself, pick() tracks the computed result. Changes only propagate when the result actually changes.

Signature

ts
function pick<T>(selector: () => T, equality?: PickEquality<T>): T

Parameters

selector

Function that computes the value to track.

  • Type: () => T
  • Required: Yes

equality

Optional equality function to compare computed values.

  • Type: PickEquality<T>
  • Default: "strict" (uses ===)

Can be:

  • "strict" - Strict equality (===)
  • "shallow" - Shallow equality (compares top-level properties)
  • "deep" - Deep equality (recursive comparison)
  • (a: T, b: T) => boolean - Custom equality function

Example

tsx
import { pick } from "storion/react";

// Without pick: re-renders when ANY profile property changes
const { profile } = useStore(({ get }) => {
  const [state] = get(userStore);
  return { profile: state.profile }; // tracks "profile"
});

// With pick: re-renders only when profile.name changes
const { name } = useStore(({ get }) => {
  const [state] = get(userStore);
  return { name: pick(() => state.profile.name) };
});

// With custom equality
const { profile } = useStore(({ get }) => {
  const [state] = get(userStore);
  return {
    profile: pick(
      () => ({ name: state.profile.name, age: state.profile.age }),
      (a, b) => a.name === b.name && a.age === b.age
    ),
  };
});

When to Use pick()

ScenarioWithout pick()With pick()
Accessing nested propertiesTracks parent objectTracks leaf
Computed valuesTracks all readsTracks specific
Large objectsAny change re-renderPrecise updates
tsx
// ❌ Without pick - re-renders on any items change
const { count } = useStore(({ get }) => {
  const [state] = get(todoStore);
  return { count: state.items.length };
});

// ✅ With pick - only re-renders when length changes
const { count } = useStore(({ get }) => {
  const [state] = get(todoStore);
  return { count: pick(() => state.items.length) };
});

pick.wrap()

Wrap functions to automatically use pick() for their results. Useful for creating reusable selectors.

Signature

ts
// Wrap a single function
pick.wrap<TArgs, TResult>(
  fn: (...args: TArgs) => TResult,
  equality?: PickEquality<TResult>
): (...args: TArgs) => TResult

// Wrap multiple methods with a prefix
pick.wrap<TPrefix, TMethods>(
  prefix: TPrefix,
  methods: TMethods,
  equality?: PickEquality<any>
): PrefixedMethods<TPrefix, TMethods>

// Wrap multiple methods without a prefix
pick.wrap<TMethods>(
  methods: TMethods,
  equality?: PickEquality<any>
): TMethods

Wrap a Single Function

ts
import { pick } from "storion/react";

const userStore = store({
  state: { firstName: "John", lastName: "Doe" },
  setup: () => ({}),
});

const stores = container();
const instance = stores.get(userStore);

// Create a wrapped selector function
const getFullName = pick.wrap(
  () => `${instance.state.firstName} ${instance.state.lastName}`,
  "strict"
);

// Use in selector - automatically uses pick()
const { fullName } = useStore(() => {
  return { fullName: getFullName() };
});

Wrap Multiple Methods with Prefix

ts
const userStore = store({
  state: { count: 0, name: "John" },
  setup: () => ({}),
});

const stores = container();
const instance = stores.get(userStore);

// Wrap multiple methods with a prefix
const methods = pick.wrap("pick", {
  count: () => instance.state.count,
  name: () => instance.state.name,
});

// Returns: { pickCount: () => pick(() => instance.state.count), pickName: () => pick(() => instance.state.name) }

// Use in selector
const { pickCount, pickName } = useStore(() => {
  return {
    pickCount: methods.pickCount(),
    pickName: methods.pickName(),
  };
});

Wrap Multiple Methods without Prefix

ts
const userStore = store({
  state: { count: 0, name: "John" },
  setup: () => ({}),
});

const stores = container();
const instance = stores.get(userStore);

// Wrap multiple methods without prefix
const methods = pick.wrap({
  count: () => instance.state.count,
  name: () => instance.state.name,
});

// Returns: { count: () => pick(() => instance.state.count), name: () => pick(() => instance.state.name) }

// Use in selector
const { count, name } = useStore(() => {
  return {
    count: methods.count(),
    name: methods.name(),
  };
});

Custom Equality with wrap()

ts
// Wrap with custom equality
const getItems = pick.wrap(
  () => instance.state.items,
  "shallow" // Use shallow equality
);

// Or with custom function
const getProfile = pick.wrap(
  () => instance.state.profile,
  (a, b) => a.id === b.id && a.name === b.name
);

Use Cases

Reusable Selectors

ts
// Create reusable selectors
const selectors = {
  fullName: pick.wrap(() => `${state.firstName} ${state.lastName}`),
  itemCount: pick.wrap(() => state.items.length),
  activeItems: pick.wrap(() => state.items.filter(i => !i.done), "shallow"),
};

// Use across multiple components
const { fullName } = useStore(() => ({ fullName: selectors.fullName() }));

Component-Local Computations

tsx
function TodoList() {
  const { stats } = useStore(({ get }) => {
    const [state] = get(todoStore);
    
    // Wrap computations for fine-grained tracking
    const getStats = pick.wrap(() => ({
      total: state.items.length,
      completed: state.items.filter(i => i.done).length,
      active: state.items.filter(i => !i.done).length,
    }), "shallow");
    
    return { stats: getStats() };
  });
  
  return (
    <div>
      <p>Total: {stats.total}</p>
      <p>Completed: {stats.completed}</p>
      <p>Active: {stats.active}</p>
    </div>
  );
}

Return Value

Returns the computed value from the selector function. The value is tracked reactively - components will only re-render when the computed result changes (according to the equality function).

See Also

Released under the MIT License.