React state management hooks for list-like components with selection support
npx @tessl/cli install tessl/npm-react-stately--list@3.13.0React state management hooks for list-like components, providing accessible and performant state management for interactive list components including listboxes, menus, and data grids. This package handles complex list interactions including selection modes, disabled items, filtering, and collection management.
npm install @react-stately/listimport { useListState, useSingleSelectListState, ListCollection, UNSTABLE_useFilteredListState } from "@react-stately/list";
import type { ListProps, ListState, SingleSelectListProps, SingleSelectListState } from "@react-stately/list";CommonJS:
const { useListState, useSingleSelectListState, ListCollection, UNSTABLE_useFilteredListState } = require("@react-stately/list");import { useListState } from "@react-stately/list";
import { Item } from "@react-stately/collections";
function MyListComponent() {
const state = useListState({
children: [
<Item key="apple">Apple</Item>,
<Item key="banana">Banana</Item>,
<Item key="orange">Orange</Item>
],
selectionMode: "multiple"
});
return (
<div>
{[...state.collection].map((item) => (
<div
key={item.key}
onClick={() => state.selectionManager.toggleSelection(item.key)}
>
{item.rendered}
</div>
))}
</div>
);
}The package is built around several key components:
ListCollection provides efficient item navigation and access methodsCreates state for list components with multiple selection support, including complex selection scenarios with disabled items and custom filtering.
/**
* Provides state management for list-like components. Handles building a collection
* of items from props, and manages multiple selection state.
*/
function useListState<T extends object>(props: ListProps<T>): ListState<T>;
interface ListProps<T> extends CollectionStateBase<T>, MultipleSelectionStateProps {
/** Filter function to generate a filtered list of nodes. */
filter?: (nodes: Iterable<Node<T>>) => Iterable<Node<T>>;
/** @private */
suppressTextValueWarning?: boolean;
/**
* A delegate object that provides layout information for items in the collection.
* This can be used to override the behavior of shift selection.
*/
layoutDelegate?: LayoutDelegate;
}
interface ListState<T> {
/** A collection of items in the list. */
collection: Collection<Node<T>>;
/** A set of items that are disabled. */
disabledKeys: Set<Key>;
/** A selection manager to read and update multiple selection state. */
selectionManager: SelectionManager;
}Creates state for list components with single selection, providing a simplified interface for components that only need single selection.
/**
* Provides state management for list-like components with single selection.
* Handles building a collection of items from props, and manages selection state.
*/
function useSingleSelectListState<T extends object>(props: SingleSelectListProps<T>): SingleSelectListState<T>;
interface SingleSelectListProps<T> extends CollectionStateBase<T>, Omit<SingleSelection, 'disallowEmptySelection'> {
/** Filter function to generate a filtered list of nodes. */
filter?: (nodes: Iterable<Node<T>>) => Iterable<Node<T>>;
/** @private */
suppressTextValueWarning?: boolean;
}
interface SingleSelectListState<T> extends ListState<T> {
/** The key for the currently selected item. */
readonly selectedKey: Key | null;
/** Sets the selected key. */
setSelectedKey(key: Key | null): void;
/** The value of the currently selected item. */
readonly selectedItem: Node<T> | null;
}Filters an existing collection using a provided filter function and returns a new ListState.
/**
* Filters a collection using the provided filter function and returns a new ListState.
* @experimental This API is unstable and may change in future versions
*/
function UNSTABLE_useFilteredListState<T extends object>(
state: ListState<T>,
filterFn: ((nodeValue: string, node: Node<T>) => boolean) | null | undefined
): ListState<T>;Collection class that provides efficient navigation and item access for list components.
/**
* Collection implementation for list components with navigation and item access methods
*/
class ListCollection<T> implements Collection<Node<T>> {
constructor(nodes: Iterable<Node<T>>);
/** Iterator over all nodes in the collection */
[Symbol.iterator](): IterableIterator<Node<T>>;
/** Returns the number of items in the collection */
get size(): number;
/** Returns an iterator of all keys in the collection */
getKeys(): IterableIterator<Key>;
/** Returns the key that comes before the given key */
getKeyBefore(key: Key): Key | null;
/** Returns the key that comes after the given key */
getKeyAfter(key: Key): Key | null;
/** Returns the first key in the collection */
getFirstKey(): Key | null;
/** Returns the last key in the collection */
getLastKey(): Key | null;
/** Returns the node for the given key */
getItem(key: Key): Node<T> | null;
/** Returns the node at the given index */
at(idx: number): Node<T> | null;
/** Returns the children of the given key */
getChildren(key: Key): Iterable<Node<T>>;
}// Re-exported from @react-types/shared and React
type Key = string | number;
type ReactNode = React.ReactNode;
type ReactElement = React.ReactElement;
interface Node<T> {
/** The type of item this node represents */
type: string;
/** A unique key for the node */
key: Key;
/** The object value the node was created from */
value: T | null;
/** The level of depth this node is at in the hierarchy */
level: number;
/** Whether this item has children, even if not loaded yet */
hasChildNodes: boolean;
/** The loaded children of this node (deprecated: Use collection.getChildren(node.key) instead) */
childNodes: Iterable<Node<T>>;
/** The rendered contents of this node (e.g. JSX) */
rendered: ReactNode;
/** A string value for this node, used for features like typeahead */
textValue: string;
/** An accessibility label for this node */
'aria-label'?: string;
/** The index of this node within its parent */
index: number;
/** A function that should be called to wrap the rendered node */
wrapper?: (element: ReactElement) => ReactElement;
/** The key of the parent node */
parentKey?: Key | null;
/** The key of the node before this node */
prevKey?: Key | null;
/** The key of the node after this node */
nextKey?: Key | null;
/** Additional properties specific to a particular node type */
props?: any;
/** @private */
shouldInvalidate?: (context: any) => boolean;
/** A function that renders this node to a React Element in the DOM */
render?: (node: Node<any>) => ReactElement;
}
interface Collection<T> {
/** The number of items in the collection */
readonly size: number;
/** Returns an iterator of all keys in the collection */
getKeys(): IterableIterator<Key>;
/** Returns the item for the given key */
getItem(key: Key): T | null;
/** Returns the item at the given index */
at(idx: number): T | null;
/** Returns the key that comes before the given key */
getKeyBefore(key: Key): Key | null;
/** Returns the key that comes after the given key */
getKeyAfter(key: Key): Key | null;
/** Returns the first key in the collection */
getFirstKey(): Key | null;
/** Returns the last key in the collection */
getLastKey(): Key | null;
/** Returns the children of the given key */
getChildren?(key: Key): Iterable<T>;
/** Returns the text value for the given key */
getTextValue?(key: Key): string;
/** Filters the collection using the provided filter function */
filter?(filterFn: (nodeValue: string, node: T) => boolean): Collection<T>;
}
interface CollectionStateBase<T> {
/** The contents of the collection */
children: ReactNode;
/** A list of keys to disable */
disabledKeys?: Key[];
}
interface MultipleSelectionStateProps {
/** The type of selection mode */
selectionMode?: SelectionMode;
/** The selection behavior for the collection */
selectionBehavior?: SelectionBehavior;
/** Whether empty selection is allowed */
disallowEmptySelection?: boolean;
/** The currently selected keys */
selectedKeys?: Selection;
/** The default selected keys (uncontrolled) */
defaultSelectedKeys?: Selection;
/** Handler called when the selection changes */
onSelectionChange?: (keys: Selection) => void;
/** The disabled keys in the collection */
disabledKeys?: Key[];
/** Whether disabledKeys applies to selection, actions, or both */
disabledBehavior?: DisabledBehavior;
}
interface SingleSelection {
/** The currently selected key */
selectedKey?: Key | null;
/** The default selected key (uncontrolled) */
defaultSelectedKey?: Key;
/** Handler called when the selection changes */
onSelectionChange?: (key: Key | null) => void;
}
interface LayoutDelegate {
/** Returns the key that should be selected when the user presses shift+arrow */
getKeyAbove?(key: Key): Key | null;
/** Returns the key that should be selected when the user presses shift+arrow */
getKeyBelow?(key: Key): Key | null;
/** Returns the key that should be selected when the user presses shift+arrow */
getKeyLeftOf?(key: Key): Key | null;
/** Returns the key that should be selected when the user presses shift+arrow */
getKeyRightOf?(key: Key): Key | null;
}
interface SelectionManager {
/** The type of selection that is allowed in the collection */
readonly selectionMode: SelectionMode;
/** The selection behavior for the collection */
readonly selectionBehavior: SelectionBehavior;
/** Whether the collection allows empty selection */
readonly disallowEmptySelection?: boolean;
/** Whether the collection is currently focused */
readonly isFocused: boolean;
/** The current focused key in the collection */
readonly focusedKey: Key | null;
/** Whether the first or last child of the focused key should receive focus */
readonly childFocusStrategy: FocusStrategy | null;
/** The currently selected keys in the collection */
readonly selectedKeys: Set<Key>;
/** Whether the selection is empty */
readonly isEmpty: boolean;
/** Whether all items in the collection are selected */
readonly isSelectAll: boolean;
/** The first selected key in the collection */
readonly firstSelectedKey: Key | null;
/** The last selected key in the collection */
readonly lastSelectedKey: Key | null;
/** The currently disabled keys in the collection */
readonly disabledKeys: Set<Key>;
/** Whether disabledKeys applies to selection, actions, or both */
readonly disabledBehavior: DisabledBehavior;
/** The collection of nodes that the selection manager handles */
collection: Collection<Node<unknown>>;
/** Sets whether the collection is focused */
setFocused(isFocused: boolean): void;
/** Sets the focused key, and optionally, whether the first or last child of that key should receive focus */
setFocusedKey(key: Key | null, child?: FocusStrategy): void;
/** Returns whether a key is selected */
isSelected(key: Key): boolean;
/** Returns whether the current selection is equal to the given selection */
isSelectionEqual(selection: Set<Key>): boolean;
/** Extends the selection to the given key */
extendSelection(toKey: Key): void;
/** Toggles whether the given key is selected */
toggleSelection(key: Key): void;
/** Replaces the selection with only the given key */
replaceSelection(key: Key): void;
/** Replaces the selection with the given keys */
setSelectedKeys(keys: Iterable<Key>): void;
/** Selects all items in the collection */
selectAll(): void;
/** Removes all keys from the selection */
clearSelection(): void;
/** Toggles between select all and an empty selection */
toggleSelectAll(): void;
/** Toggles, replaces, or extends selection to the given key depending on the pointer event and collection's selection mode */
select(key: Key, e?: PressEvent | LongPressEvent | PointerEvent): void;
/** Returns whether the given key can be selected */
canSelectItem(key: Key): boolean;
/** Returns whether the given key is non-interactive, i.e. both selection and actions are disabled */
isDisabled(key: Key): boolean;
/** Sets the selection behavior for the collection */
setSelectionBehavior(selectionBehavior: SelectionBehavior): void;
/** Returns whether the given key is a hyperlink */
isLink(key: Key): boolean;
/** Returns the props for the given item */
getItemProps(key: Key): any;
}
type SelectionMode = 'none' | 'single' | 'multiple';
type Selection = 'all' | Set<Key>;
type SelectionBehavior = 'toggle' | 'replace';
type DisabledBehavior = 'selection' | 'all';
type FocusStrategy = 'first' | 'last';import { useListState } from "@react-stately/list";
import { Item } from "@react-stately/collections";
const fruits = [
{ id: "1", name: "Apple", color: "red" },
{ id: "2", name: "Banana", color: "yellow" },
{ id: "3", name: "Orange", color: "orange" },
{ id: "4", name: "Grape", color: "purple" }
];
function FilterableList() {
const [searchTerm, setSearchTerm] = useState("");
const state = useListState({
children: fruits.map(fruit => (
<Item key={fruit.id} textValue={fruit.name}>
{fruit.name} ({fruit.color})
</Item>
)),
selectionMode: "multiple",
filter: searchTerm
? (nodes) => [...nodes].filter(node =>
node.textValue.toLowerCase().includes(searchTerm.toLowerCase())
)
: undefined
});
return (
<div>
<input
type="text"
placeholder="Search fruits..."
onChange={(e) => setSearchTerm(e.target.value)}
/>
<div>
{[...state.collection].map((item) => (
<div
key={item.key}
onClick={() => state.selectionManager.toggleSelection(item.key)}
style={{
backgroundColor: state.selectionManager.isSelected(item.key) ? "#e3f2fd" : "white"
}}
>
{item.rendered}
</div>
))}
</div>
</div>
);
}import { useSingleSelectListState } from "@react-stately/list";
import { Item } from "@react-stately/collections";
function SingleSelectList() {
const [selectedKey, setSelectedKey] = useState<Key | null>("1");
const state = useSingleSelectListState({
children: [
<Item key="1">Option 1</Item>,
<Item key="2">Option 2</Item>,
<Item key="3">Option 3</Item>
],
selectedKey,
onSelectionChange: setSelectedKey
});
return (
<div>
<p>Selected: {state.selectedItem?.rendered || "None"}</p>
<div>
{[...state.collection].map((item) => (
<button
key={item.key}
onClick={() => state.setSelectedKey(item.key)}
style={{
backgroundColor: item.key === state.selectedKey ? "#1976d2" : "#f5f5f5",
color: item.key === state.selectedKey ? "white" : "black"
}}
>
{item.rendered}
</button>
))}
</div>
</div>
);
}import { useListState } from "@react-stately/list";
import { Item } from "@react-stately/collections";
function ListWithDisabledItems() {
const state = useListState({
children: [
<Item key="available1">Available Item 1</Item>,
<Item key="disabled1">Disabled Item 1</Item>,
<Item key="available2">Available Item 2</Item>,
<Item key="disabled2">Disabled Item 2</Item>
],
disabledKeys: ["disabled1", "disabled2"],
selectionMode: "multiple"
});
return (
<div>
{[...state.collection].map((item) => {
const isDisabled = state.disabledKeys.has(item.key);
return (
<div
key={item.key}
onClick={!isDisabled ? () => state.selectionManager.toggleSelection(item.key) : undefined}
style={{
opacity: isDisabled ? 0.5 : 1,
cursor: isDisabled ? "not-allowed" : "pointer",
backgroundColor: state.selectionManager.isSelected(item.key) ? "#e3f2fd" : "white"
}}
>
{item.rendered}
</div>
);
})}
</div>
);
}