or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

index.md
tile.json

tessl/npm-react-stately--list

React state management hooks for list-like components with selection support

Workspace
tessl
Visibility
Public
Created
Last updated
Describes
npmpkg:npm/@react-stately/list@3.13.x

To install, run

npx @tessl/cli install tessl/npm-react-stately--list@3.13.0

index.mddocs/

@react-stately/list

React 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.

Package Information

  • Package Name: @react-stately/list
  • Package Type: npm
  • Language: TypeScript
  • Installation: npm install @react-stately/list

Core Imports

import { 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");

Basic Usage

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>
  );
}

Architecture

The package is built around several key components:

  • Collection Management: ListCollection provides efficient item navigation and access methods
  • State Management: Hooks that integrate with React Stately's selection system and collection utilities
  • Selection Handling: Built-in support for multiple and single selection patterns with accessibility features
  • Focus Management: Automatic focus handling when items are added/removed from collections
  • Filtering Support: Optional filtering capabilities for dynamic list content

Capabilities

Multiple Selection Lists

Creates 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;
}

Single Selection Lists

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;
}

Filtered List State (Unstable)

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 Implementation

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>>;
}

Types

Core Types

// 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';

Usage Examples

Multiple Selection with Filtering

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>
  );
}

Single Selection with Controlled State

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>
  );
}

Working with Disabled Items

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>
  );
}