npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@leesf/use-selection

v0.1.2

Published

createSelector from SolidJS but in React

Readme

@leesf/use-selection

React utilities for keyed selection state. It is useful for large selectable lists where changing the selected item should update only the previously selected row, the newly selected row, and any selected-value display.

The API is similar in spirit to Solid's createSelector: each row subscribes to whether its own key is selected instead of receiving the selected key as a prop from the parent list.

Installation

npm install @leesf/use-selection
yarn add @leesf/use-selection
pnpm add @leesf/use-selection
bun add @leesf/use-selection

React is a peer dependency.

Components (Recommended)

Use SelectionProvider with SelectedKey and IsSelectedKey when you do not want to pass the store through every row. This is the recommended path for list UIs because selected state stays local to the components that actually need it.

import {
  IsSelectedKey,
  SelectionProvider,
  SelectedKey,
  useSelectionStore,
} from "@leesf/use-selection";

const items = [
  { id: "one", label: "One" },
  { id: "two", label: "Two" },
  { id: "three", label: "Three" },
];

export function List() {
  return (
    <SelectionProvider initialKey={items[0]!.id}>
      <SelectedKey>
        {(selectedKey) => (
          <p>Selected: {(selectedKey as string | null) ?? "None"}</p>
        )}
      </SelectedKey>

      {items.map((item) => (
        <IsSelectedKey key={item.id} keyValue={item.id}>
          {(isSelected) => <Row isSelected={isSelected} item={item} />}
        </IsSelectedKey>
      ))}
    </SelectionProvider>
  );
}

function Row({
  isSelected,
  item,
}: {
  isSelected: boolean;
  item: { id: string; label: string };
}) {
  const store = useSelectionStore();

  return (
    <button
      aria-pressed={isSelected}
      onClick={() => store.setSelectedKey(item.id)}
      type="button"
    >
      {item.label}
    </button>
  );
}

You can also provide a store directly when you want to own it outside the provider:

const store = createSelectionStore("one");

<SelectionProvider store={store}>
  <SelectedKey>{(selectedKey) => selectedKey}</SelectedKey>
</SelectionProvider>;

Use keyValue with IsSelectedKey because key is a special React prop and is not passed through to components.

Without Provider

You can pass a store directly to the wrapper components. This avoids context and keeps TypeScript inference from the store.

import {
  createSelectionStore,
  IsSelectedKey,
  SelectedKey,
} from "@leesf/use-selection";
import { useMemo } from "react";

const items = [
  { id: "one", label: "One" },
  { id: "two", label: "Two" },
];

function ListWithoutProvider() {
  const store = useMemo(() => createSelectionStore(items[0]!.id), []);

  return (
    <div>
      <SelectedKey store={store}>
        {(selectedKey) => <p>Selected: {selectedKey ?? "None"}</p>}
      </SelectedKey>

      {items.map((item) => (
        <IsSelectedKey key={item.id} keyValue={item.id} store={store}>
          {(isSelected) => (
            <RowWithoutProvider
              handleSelect={store.setSelectedKey}
              isSelected={isSelected}
              item={item}
            />
          )}
        </IsSelectedKey>
      ))}
    </div>
  );
}

function RowWithoutProvider({
  handleSelect,
  isSelected,
  item,
}: {
  handleSelect: (id: string) => void;
  isSelected: boolean;
  item: { id: string; label: string };
}) {
  return (
    <button
      aria-pressed={isSelected}
      onClick={() => handleSelect(item.id)}
      type="button"
    >
      {item.label}
    </button>
  );
}

In this example, selectedKey is inferred as string | null because store is SelectionStore<string>.

Hooks

The hooks are exported for advanced cases, but the wrapper components are the recommended default. Components make it harder to accidentally pull selected state into the parent list and rerender every row.

import {
  createSelectionStore,
  useIsSelectedKey,
  useSelectedKey,
} from "@leesf/use-selection";
import { useMemo } from "react";

function HookList() {
  const store = useMemo(() => createSelectionStore("one"), []);
  const selectedKey = useSelectedKey(store);

  return (
    <div>
      <p>Selected: {selectedKey ?? "None"}</p>
      <HookRow handleSelect={store.setSelectedKey} id="one" store={store} />
      <HookRow handleSelect={store.setSelectedKey} id="two" store={store} />
    </div>
  );
}

function HookRow({
  handleSelect,
  id,
  store,
}: {
  handleSelect: (id: string) => void;
  id: string;
  store: ReturnType<typeof createSelectionStore<string>>;
}) {
  const isSelected = useIsSelectedKey(store, id);

  return (
    <button
      aria-pressed={isSelected}
      onClick={() => handleSelect(id)}
      type="button"
    >
      {id}
    </button>
  );
}

API

createSelectionStore(initialKey?)

Creates a selection store.

const store = createSelectionStore<string>("item-1");

The store exposes:

  • setSelectedKey(next) - sets the selected key.
  • getSelectedKeySnapshot() - returns the current selected key.
  • subscribeSelectedKey(listener) - subscribes to selected-key changes.
  • getIsSelectedKeySnapshot(key) - returns whether key is selected.
  • subscribeIsSelectedKey(key, listener) - subscribes to changes for one key.

SelectionProvider

Provides a selection store to SelectedKey, IsSelectedKey, and useSelectionStore.

Create an internal store:

<SelectionProvider initialKey="item-1">{children}</SelectionProvider>

Provide an existing store:

const store = createSelectionStore("item-1");

<SelectionProvider store={store}>{children}</SelectionProvider>;

initialKey is only used when the provider creates its internal store.

useSelectionStore()

Reads the nearest SelectionProvider.

const store = useSelectionStore();

useSelectedKey(store)

Subscribes to the selected key value.

const selectedKey = useSelectedKey(store);

useIsSelectedKey(store, key)

Subscribes to whether a specific key is selected.

const isSelected = useIsSelectedKey(store, item.id);

SelectedKey

Component wrapper around useSelectedKey.

<SelectedKey store={store}>
  {(selectedKey) => <span>{selectedKey}</span>}
</SelectedKey>

IsSelectedKey

Component wrapper around useIsSelectedKey.

<IsSelectedKey keyValue={item.id} store={store}>
  {(isSelected) => <Row selected={isSelected} />}
</IsSelectedKey>

Why Not Keep Selected Key In Parent State?

If a parent list stores selectedId in React state and passes it to every row, each selection change rerenders the parent and gives every row a changed prop. That is often fine for small lists, but expensive for large or complex rows.

With @leesf/use-selection, rows subscribe by key. Updating the selected key notifies only:

  • the previously selected key
  • the newly selected key
  • subscribers to the selected key value

Playground

Run the playground to compare normal React state selection with keyed subscriptions:

pnpm run play

Build and preview the production playground:

pnpm run play:build
pnpm run play:preview

The playground shows per-row render counts so you can see the difference when selecting items.

Development

pnpm install
pnpm run typecheck
pnpm run test
pnpm run build