React state management hooks for list-like components with selection support
npx @tessl/cli install tessl/npm-react-stately--list@3.13.00
# @react-stately/list
1
2
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.
3
4
## Package Information
5
6
- **Package Name**: @react-stately/list
7
- **Package Type**: npm
8
- **Language**: TypeScript
9
- **Installation**: `npm install @react-stately/list`
10
11
## Core Imports
12
13
```typescript
14
import { useListState, useSingleSelectListState, ListCollection, UNSTABLE_useFilteredListState } from "@react-stately/list";
15
import type { ListProps, ListState, SingleSelectListProps, SingleSelectListState } from "@react-stately/list";
16
```
17
18
CommonJS:
19
20
```javascript
21
const { useListState, useSingleSelectListState, ListCollection, UNSTABLE_useFilteredListState } = require("@react-stately/list");
22
```
23
24
## Basic Usage
25
26
```typescript
27
import { useListState } from "@react-stately/list";
28
import { Item } from "@react-stately/collections";
29
30
function MyListComponent() {
31
const state = useListState({
32
children: [
33
<Item key="apple">Apple</Item>,
34
<Item key="banana">Banana</Item>,
35
<Item key="orange">Orange</Item>
36
],
37
selectionMode: "multiple"
38
});
39
40
return (
41
<div>
42
{[...state.collection].map((item) => (
43
<div
44
key={item.key}
45
onClick={() => state.selectionManager.toggleSelection(item.key)}
46
>
47
{item.rendered}
48
</div>
49
))}
50
</div>
51
);
52
}
53
```
54
55
## Architecture
56
57
The package is built around several key components:
58
59
- **Collection Management**: `ListCollection` provides efficient item navigation and access methods
60
- **State Management**: Hooks that integrate with React Stately's selection system and collection utilities
61
- **Selection Handling**: Built-in support for multiple and single selection patterns with accessibility features
62
- **Focus Management**: Automatic focus handling when items are added/removed from collections
63
- **Filtering Support**: Optional filtering capabilities for dynamic list content
64
65
## Capabilities
66
67
### Multiple Selection Lists
68
69
Creates state for list components with multiple selection support, including complex selection scenarios with disabled items and custom filtering.
70
71
```typescript { .api }
72
/**
73
* Provides state management for list-like components. Handles building a collection
74
* of items from props, and manages multiple selection state.
75
*/
76
function useListState<T extends object>(props: ListProps<T>): ListState<T>;
77
78
interface ListProps<T> extends CollectionStateBase<T>, MultipleSelectionStateProps {
79
/** Filter function to generate a filtered list of nodes. */
80
filter?: (nodes: Iterable<Node<T>>) => Iterable<Node<T>>;
81
/** @private */
82
suppressTextValueWarning?: boolean;
83
/**
84
* A delegate object that provides layout information for items in the collection.
85
* This can be used to override the behavior of shift selection.
86
*/
87
layoutDelegate?: LayoutDelegate;
88
}
89
90
interface ListState<T> {
91
/** A collection of items in the list. */
92
collection: Collection<Node<T>>;
93
/** A set of items that are disabled. */
94
disabledKeys: Set<Key>;
95
/** A selection manager to read and update multiple selection state. */
96
selectionManager: SelectionManager;
97
}
98
```
99
100
### Single Selection Lists
101
102
Creates state for list components with single selection, providing a simplified interface for components that only need single selection.
103
104
```typescript { .api }
105
/**
106
* Provides state management for list-like components with single selection.
107
* Handles building a collection of items from props, and manages selection state.
108
*/
109
function useSingleSelectListState<T extends object>(props: SingleSelectListProps<T>): SingleSelectListState<T>;
110
111
interface SingleSelectListProps<T> extends CollectionStateBase<T>, Omit<SingleSelection, 'disallowEmptySelection'> {
112
/** Filter function to generate a filtered list of nodes. */
113
filter?: (nodes: Iterable<Node<T>>) => Iterable<Node<T>>;
114
/** @private */
115
suppressTextValueWarning?: boolean;
116
}
117
118
interface SingleSelectListState<T> extends ListState<T> {
119
/** The key for the currently selected item. */
120
readonly selectedKey: Key | null;
121
/** Sets the selected key. */
122
setSelectedKey(key: Key | null): void;
123
/** The value of the currently selected item. */
124
readonly selectedItem: Node<T> | null;
125
}
126
```
127
128
### Filtered List State (Unstable)
129
130
Filters an existing collection using a provided filter function and returns a new ListState.
131
132
```typescript { .api }
133
/**
134
* Filters a collection using the provided filter function and returns a new ListState.
135
* @experimental This API is unstable and may change in future versions
136
*/
137
function UNSTABLE_useFilteredListState<T extends object>(
138
state: ListState<T>,
139
filterFn: ((nodeValue: string, node: Node<T>) => boolean) | null | undefined
140
): ListState<T>;
141
```
142
143
### Collection Implementation
144
145
Collection class that provides efficient navigation and item access for list components.
146
147
```typescript { .api }
148
/**
149
* Collection implementation for list components with navigation and item access methods
150
*/
151
class ListCollection<T> implements Collection<Node<T>> {
152
constructor(nodes: Iterable<Node<T>>);
153
154
/** Iterator over all nodes in the collection */
155
[Symbol.iterator](): IterableIterator<Node<T>>;
156
157
/** Returns the number of items in the collection */
158
get size(): number;
159
160
/** Returns an iterator of all keys in the collection */
161
getKeys(): IterableIterator<Key>;
162
163
/** Returns the key that comes before the given key */
164
getKeyBefore(key: Key): Key | null;
165
166
/** Returns the key that comes after the given key */
167
getKeyAfter(key: Key): Key | null;
168
169
/** Returns the first key in the collection */
170
getFirstKey(): Key | null;
171
172
/** Returns the last key in the collection */
173
getLastKey(): Key | null;
174
175
/** Returns the node for the given key */
176
getItem(key: Key): Node<T> | null;
177
178
/** Returns the node at the given index */
179
at(idx: number): Node<T> | null;
180
181
/** Returns the children of the given key */
182
getChildren(key: Key): Iterable<Node<T>>;
183
}
184
```
185
186
## Types
187
188
### Core Types
189
190
```typescript { .api }
191
// Re-exported from @react-types/shared and React
192
type Key = string | number;
193
type ReactNode = React.ReactNode;
194
type ReactElement = React.ReactElement;
195
196
interface Node<T> {
197
/** The type of item this node represents */
198
type: string;
199
/** A unique key for the node */
200
key: Key;
201
/** The object value the node was created from */
202
value: T | null;
203
/** The level of depth this node is at in the hierarchy */
204
level: number;
205
/** Whether this item has children, even if not loaded yet */
206
hasChildNodes: boolean;
207
/** The loaded children of this node (deprecated: Use collection.getChildren(node.key) instead) */
208
childNodes: Iterable<Node<T>>;
209
/** The rendered contents of this node (e.g. JSX) */
210
rendered: ReactNode;
211
/** A string value for this node, used for features like typeahead */
212
textValue: string;
213
/** An accessibility label for this node */
214
'aria-label'?: string;
215
/** The index of this node within its parent */
216
index: number;
217
/** A function that should be called to wrap the rendered node */
218
wrapper?: (element: ReactElement) => ReactElement;
219
/** The key of the parent node */
220
parentKey?: Key | null;
221
/** The key of the node before this node */
222
prevKey?: Key | null;
223
/** The key of the node after this node */
224
nextKey?: Key | null;
225
/** Additional properties specific to a particular node type */
226
props?: any;
227
/** @private */
228
shouldInvalidate?: (context: any) => boolean;
229
/** A function that renders this node to a React Element in the DOM */
230
render?: (node: Node<any>) => ReactElement;
231
}
232
233
interface Collection<T> {
234
/** The number of items in the collection */
235
readonly size: number;
236
/** Returns an iterator of all keys in the collection */
237
getKeys(): IterableIterator<Key>;
238
/** Returns the item for the given key */
239
getItem(key: Key): T | null;
240
/** Returns the item at the given index */
241
at(idx: number): T | null;
242
/** Returns the key that comes before the given key */
243
getKeyBefore(key: Key): Key | null;
244
/** Returns the key that comes after the given key */
245
getKeyAfter(key: Key): Key | null;
246
/** Returns the first key in the collection */
247
getFirstKey(): Key | null;
248
/** Returns the last key in the collection */
249
getLastKey(): Key | null;
250
/** Returns the children of the given key */
251
getChildren?(key: Key): Iterable<T>;
252
/** Returns the text value for the given key */
253
getTextValue?(key: Key): string;
254
/** Filters the collection using the provided filter function */
255
filter?(filterFn: (nodeValue: string, node: T) => boolean): Collection<T>;
256
}
257
258
interface CollectionStateBase<T> {
259
/** The contents of the collection */
260
children: ReactNode;
261
/** A list of keys to disable */
262
disabledKeys?: Key[];
263
}
264
265
interface MultipleSelectionStateProps {
266
/** The type of selection mode */
267
selectionMode?: SelectionMode;
268
/** The selection behavior for the collection */
269
selectionBehavior?: SelectionBehavior;
270
/** Whether empty selection is allowed */
271
disallowEmptySelection?: boolean;
272
/** The currently selected keys */
273
selectedKeys?: Selection;
274
/** The default selected keys (uncontrolled) */
275
defaultSelectedKeys?: Selection;
276
/** Handler called when the selection changes */
277
onSelectionChange?: (keys: Selection) => void;
278
/** The disabled keys in the collection */
279
disabledKeys?: Key[];
280
/** Whether disabledKeys applies to selection, actions, or both */
281
disabledBehavior?: DisabledBehavior;
282
}
283
284
interface SingleSelection {
285
/** The currently selected key */
286
selectedKey?: Key | null;
287
/** The default selected key (uncontrolled) */
288
defaultSelectedKey?: Key;
289
/** Handler called when the selection changes */
290
onSelectionChange?: (key: Key | null) => void;
291
}
292
293
interface LayoutDelegate {
294
/** Returns the key that should be selected when the user presses shift+arrow */
295
getKeyAbove?(key: Key): Key | null;
296
/** Returns the key that should be selected when the user presses shift+arrow */
297
getKeyBelow?(key: Key): Key | null;
298
/** Returns the key that should be selected when the user presses shift+arrow */
299
getKeyLeftOf?(key: Key): Key | null;
300
/** Returns the key that should be selected when the user presses shift+arrow */
301
getKeyRightOf?(key: Key): Key | null;
302
}
303
304
interface SelectionManager {
305
/** The type of selection that is allowed in the collection */
306
readonly selectionMode: SelectionMode;
307
/** The selection behavior for the collection */
308
readonly selectionBehavior: SelectionBehavior;
309
/** Whether the collection allows empty selection */
310
readonly disallowEmptySelection?: boolean;
311
/** Whether the collection is currently focused */
312
readonly isFocused: boolean;
313
/** The current focused key in the collection */
314
readonly focusedKey: Key | null;
315
/** Whether the first or last child of the focused key should receive focus */
316
readonly childFocusStrategy: FocusStrategy | null;
317
/** The currently selected keys in the collection */
318
readonly selectedKeys: Set<Key>;
319
/** Whether the selection is empty */
320
readonly isEmpty: boolean;
321
/** Whether all items in the collection are selected */
322
readonly isSelectAll: boolean;
323
/** The first selected key in the collection */
324
readonly firstSelectedKey: Key | null;
325
/** The last selected key in the collection */
326
readonly lastSelectedKey: Key | null;
327
/** The currently disabled keys in the collection */
328
readonly disabledKeys: Set<Key>;
329
/** Whether disabledKeys applies to selection, actions, or both */
330
readonly disabledBehavior: DisabledBehavior;
331
/** The collection of nodes that the selection manager handles */
332
collection: Collection<Node<unknown>>;
333
334
/** Sets whether the collection is focused */
335
setFocused(isFocused: boolean): void;
336
/** Sets the focused key, and optionally, whether the first or last child of that key should receive focus */
337
setFocusedKey(key: Key | null, child?: FocusStrategy): void;
338
/** Returns whether a key is selected */
339
isSelected(key: Key): boolean;
340
/** Returns whether the current selection is equal to the given selection */
341
isSelectionEqual(selection: Set<Key>): boolean;
342
/** Extends the selection to the given key */
343
extendSelection(toKey: Key): void;
344
/** Toggles whether the given key is selected */
345
toggleSelection(key: Key): void;
346
/** Replaces the selection with only the given key */
347
replaceSelection(key: Key): void;
348
/** Replaces the selection with the given keys */
349
setSelectedKeys(keys: Iterable<Key>): void;
350
/** Selects all items in the collection */
351
selectAll(): void;
352
/** Removes all keys from the selection */
353
clearSelection(): void;
354
/** Toggles between select all and an empty selection */
355
toggleSelectAll(): void;
356
/** Toggles, replaces, or extends selection to the given key depending on the pointer event and collection's selection mode */
357
select(key: Key, e?: PressEvent | LongPressEvent | PointerEvent): void;
358
/** Returns whether the given key can be selected */
359
canSelectItem(key: Key): boolean;
360
/** Returns whether the given key is non-interactive, i.e. both selection and actions are disabled */
361
isDisabled(key: Key): boolean;
362
/** Sets the selection behavior for the collection */
363
setSelectionBehavior(selectionBehavior: SelectionBehavior): void;
364
/** Returns whether the given key is a hyperlink */
365
isLink(key: Key): boolean;
366
/** Returns the props for the given item */
367
getItemProps(key: Key): any;
368
}
369
370
type SelectionMode = 'none' | 'single' | 'multiple';
371
type Selection = 'all' | Set<Key>;
372
type SelectionBehavior = 'toggle' | 'replace';
373
type DisabledBehavior = 'selection' | 'all';
374
type FocusStrategy = 'first' | 'last';
375
```
376
377
## Usage Examples
378
379
### Multiple Selection with Filtering
380
381
```typescript
382
import { useListState } from "@react-stately/list";
383
import { Item } from "@react-stately/collections";
384
385
const fruits = [
386
{ id: "1", name: "Apple", color: "red" },
387
{ id: "2", name: "Banana", color: "yellow" },
388
{ id: "3", name: "Orange", color: "orange" },
389
{ id: "4", name: "Grape", color: "purple" }
390
];
391
392
function FilterableList() {
393
const [searchTerm, setSearchTerm] = useState("");
394
395
const state = useListState({
396
children: fruits.map(fruit => (
397
<Item key={fruit.id} textValue={fruit.name}>
398
{fruit.name} ({fruit.color})
399
</Item>
400
)),
401
selectionMode: "multiple",
402
filter: searchTerm
403
? (nodes) => [...nodes].filter(node =>
404
node.textValue.toLowerCase().includes(searchTerm.toLowerCase())
405
)
406
: undefined
407
});
408
409
return (
410
<div>
411
<input
412
type="text"
413
placeholder="Search fruits..."
414
onChange={(e) => setSearchTerm(e.target.value)}
415
/>
416
<div>
417
{[...state.collection].map((item) => (
418
<div
419
key={item.key}
420
onClick={() => state.selectionManager.toggleSelection(item.key)}
421
style={{
422
backgroundColor: state.selectionManager.isSelected(item.key) ? "#e3f2fd" : "white"
423
}}
424
>
425
{item.rendered}
426
</div>
427
))}
428
</div>
429
</div>
430
);
431
}
432
```
433
434
### Single Selection with Controlled State
435
436
```typescript
437
import { useSingleSelectListState } from "@react-stately/list";
438
import { Item } from "@react-stately/collections";
439
440
function SingleSelectList() {
441
const [selectedKey, setSelectedKey] = useState<Key | null>("1");
442
443
const state = useSingleSelectListState({
444
children: [
445
<Item key="1">Option 1</Item>,
446
<Item key="2">Option 2</Item>,
447
<Item key="3">Option 3</Item>
448
],
449
selectedKey,
450
onSelectionChange: setSelectedKey
451
});
452
453
return (
454
<div>
455
<p>Selected: {state.selectedItem?.rendered || "None"}</p>
456
<div>
457
{[...state.collection].map((item) => (
458
<button
459
key={item.key}
460
onClick={() => state.setSelectedKey(item.key)}
461
style={{
462
backgroundColor: item.key === state.selectedKey ? "#1976d2" : "#f5f5f5",
463
color: item.key === state.selectedKey ? "white" : "black"
464
}}
465
>
466
{item.rendered}
467
</button>
468
))}
469
</div>
470
</div>
471
);
472
}
473
```
474
475
### Working with Disabled Items
476
477
```typescript
478
import { useListState } from "@react-stately/list";
479
import { Item } from "@react-stately/collections";
480
481
function ListWithDisabledItems() {
482
const state = useListState({
483
children: [
484
<Item key="available1">Available Item 1</Item>,
485
<Item key="disabled1">Disabled Item 1</Item>,
486
<Item key="available2">Available Item 2</Item>,
487
<Item key="disabled2">Disabled Item 2</Item>
488
],
489
disabledKeys: ["disabled1", "disabled2"],
490
selectionMode: "multiple"
491
});
492
493
return (
494
<div>
495
{[...state.collection].map((item) => {
496
const isDisabled = state.disabledKeys.has(item.key);
497
return (
498
<div
499
key={item.key}
500
onClick={!isDisabled ? () => state.selectionManager.toggleSelection(item.key) : undefined}
501
style={{
502
opacity: isDisabled ? 0.5 : 1,
503
cursor: isDisabled ? "not-allowed" : "pointer",
504
backgroundColor: state.selectionManager.isSelected(item.key) ? "#e3f2fd" : "white"
505
}}
506
>
507
{item.rendered}
508
</div>
509
);
510
})}
511
</div>
512
);
513
}
514
```