Skip to content

Selection

Selection components in react-core handle selection using keys (Key values) rather than storing full objects. This section documents how selection state is represented and how to resolve selected items. These components include GridList, ListBox, Select and Combobox.

Single Selection

Data Structure

For single selection, the state value is a Key | null.

  • Key is typically a string | number
  • null indicates that no item is selected
const [selectedKey, setSelectedKey] = React.useState<Key | null>(null);

Lookup Example

The selected key corresponds to an item in the provided dataset. A lookup map can be created to resolve the full object:

type Item = { id: Key; name: string };
const items: Item[] = [
{ id: "1", name: "First" },
{ id: "2", name: "Second" },
];
const itemMap = React.useMemo(
() => new Map(items.map((i) => [i.id, i])),
[items]
);
const selectedItem = selectedKey ? itemMap.get(selectedKey) : null;

Instead of a Map, you could also just use .find on the original array. This is less performant than a Map and will require a scan of the entire array for every lookup. This is really only recommended for very small datasets.

const selectedItem =
selectedKey != null ? items.find((it) => it.id === selectedKey) ?? null;

Multiple Selection

Data Structure

For multiple selection, the state value is of type Selection.

  • Selection is either a Set<Key> containing the selected keys, or the string "all" to indicate that every item is selected.
const [selectedKeys, setSelectedKeys] = React.useState<Selection>(
new Set<Key>()
);

Select All

When the value is "all", every item in the collection is considered selected.

if (selectedKeys === "all") {
// all items are selected
}

Lookups

Selected items are resolved by looking up each key in the dataset:

type Item = { id: Key; name: string };
const items: Item[] = [
{ id: "1", name: "First" },
{ id: "2", name: "Second" },
];
const itemMap = React.useMemo(
() => new Map(items.map((i) => [i.id, i])),
[items]
);
const selectedItems =
selectedKeys === "all"
? items
: Array.from(selectedKeys as Set<Key>, (key) => itemMap.get(key)).filter(
Boolean
);

Advantages to Key-Based Approach

Selection state is managed with keys rather than entire objects, below are some of the reasons that we think make this the correct approach.

🔑 Stable identity Keys are primitives (string or number), so equality checks use ===. This avoids issues with object references that may change on re-renders or data updates.

📦 Immutable updates Updating selection means replacing a primitive key or creating a new Set<Key>. React can detect these changes efficiently with shallow comparisons, without needing to diff or clone entire objects.

📚 Single source of truth The dataset itself is authoritative. Selection state only tracks which items are selected, not a copy of the item data. This prevents stale or redundant objects from living in component state.

Fast lookups With a Map<Key, Item>, resolving a key to an item is an O(1) operation. This is faster and simpler than repeatedly scanning arrays with .find.

💾 Easy persistence Keys are lightweight and serializable. They can be saved in localStorage, embedded in query strings, or sent over a network. Full objects are bulkier and harder to store consistently.

📊 Virtualization & async friendly Keys remain valid even when the full dataset is not loaded. This is important for large or virtualized lists, or when items are fetched incrementally.

🔄 Consistent with React Aria React Aria components (Select, ListBox, Combobox, Gridlist) all expose selection APIs based on keys (selectedKey, selectedKeys, onSelectionChange). This ensures consistent behavior across controls.