# AsyncList

**Loads, filters and pages a list of items asynchronously, without any DOM. A thin wrapper around the machine of `@zag-js/async-list`.**

`AsyncList` has no rendering of its own: you create it with a `load` function, subscribe to it and render the items yourself. The Combobox [Async Search](/docs/components/combobox.md) example uses it to fill the list from an Extbase action while the user types.

## Usage

```typescript
import { debounce } from '@zag-js/utils';
import { AsyncList, extbase } from 'fluid-primitives';

interface City {
    value: string;
    title: string;
}

const list = new AsyncList<City>({
    load: async ({ filter, signal }) => {
        const response = await extbase.post(searchUrl, { q: filter }, { signal });
        return { items: (await response.json()) as City[] };
    },
});

list.subscribe(api => {
    renderItems(api.items);
    spinnerEl.hidden = !api.isLoading;
});

const setFilter = debounce((filter: string) => list.setFilter(filter), 300);
inputEl.addEventListener('input', () => setFilter(inputEl.value));

list.init();
```

`setFilter()` reloads the list straight away. Debouncing it is up to you, so you decide the delay and can skip it where it isn't needed, e.g. when the list only reloads from `dependencies`. Call `list.destroy()` when you are done with it.

## Options

The options of an `AsyncList`: the props of the `@zag-js/async-list` machine.

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `load` | `(args: LoadDetails<Item, Filter, Sorting, Cursor>) => Promise<LoadResult<Item, Cursor>>` | Yes | `-` | The function to call when the list is loaded |
| `sort` | `((args: SortDetails<Item, Filter, Sorting>) => Promise<{ items: Item[]; }> \| { items: Item[]; } \| undefined) \| undefined` | No | `-` | The function to call when the list is sorted client-side. When provided, sorting is performed locally instead of triggering a new load. |
| `initialItems` | `Item[] \| undefined` | No | `-` | The initial items to display |
| `initialSorting` | `Sorting \| undefined` | No | `-` | The initial sorting state |
| `initialFilter` | `Filter \| undefined` | No | `-` | The initial filter state |
| `keepPreviousItems` | `boolean \| undefined` | No | `false` | Whether to keep the previous items visible while new data is loading. Prevents content flash when filter or sort changes. |
| `dependencies` | `LoadDependency[] \| undefined` | No | `-` | The dependencies to watch for changes |
| `autoReload` | `boolean \| undefined` | No | `-` | Whether to automatically reload the list when the dependencies change |
| `onSuccess` | `((details: { items: Item[]; }) => void \| undefined) \| undefined` | No | `-` | The function to call when the list is loaded successfully |
| `onError` | `((details: { error: Error; }) => void \| undefined) \| undefined` | No | `-` | The function to call when the list fails to load |

## API

Wraps the machine of `@zag-js/async-list` for use without any DOM or hydration: construct it, call `init()`, `subscribe()` to state changes and `destroy()` when done. Rendering is yours, do it from inside `subscribe()`.

Debouncing or throttling `setFilter()` is left to the caller, wrap the call site with the `debounce` or `throttle` of `@zag-js/utils`, which this library already depends on.

```typescript
class AsyncList<T, Filter = string, Sorting = asyncList.SortDescriptor<T>, Cursor = string>
```

### Constructor

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `options` | `AsyncListOptions<T, Filter, Sorting, Cursor>` | Yes | `-` |  |

### Properties

| Name | Type | Description |
| --- | --- | --- |
| `api` | `asyncList.Api<T, Filter, Sorting, Cursor>` | The api of the current state of the list. It is read fresh on every access, don't hold on to it. |

### Methods

#### subscribe()

Calls `fn` with the api on every future state change. It does not fire for the current state, read `api` first if you need it.

```typescript
subscribe(fn: (api: asyncList.Api<T, Filter, Sorting, Cursor>) => void): () => void
```

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `fn` | `(api: asyncList.Api<T, Filter, Sorting, Cursor>) => void` | Yes | `-` | The listener. |

**Returns** `() => void`. A function that removes the listener.

#### setFilter()

Sets the filter and reloads the list with it.

```typescript
setFilter(filter: Filter): void
```

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `filter` | `Filter` | Yes | `-` |  |

#### updateProps()

Changes options at runtime, e.g. `dependencies` to trigger an `autoReload`. `load` is fixed when the list is constructed and can't be swapped here.

```typescript
updateProps(newProps: Partial<Omit<asyncList.Props<T, Filter, Sorting, Cursor>, 'load'>>): void
```

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `newProps` | `Partial<Omit<asyncList.Props<T, Filter, Sorting, Cursor>, 'load'>>` | Yes | `-` |  |

#### init()

Starts the machine, which loads the list for the first time. Call it once, after constructing.

```typescript
init(): void
```

#### destroy()

Stops the machine and cancels a load that is in flight.

```typescript
destroy(): void
```

## The api object

`list.api` and the argument of a `subscribe()` listener are the api of the machine:

### Machine JavaScript API

| Name | Type | Description |
| --- | --- | --- |
| `items` | `Item[]` | The items in the list. |
| `filter` | `Filter` | The current filter state. |
| `cursor` | `Cursor \| undefined` | The cursor for pagination. |
| `sorting` | `Sorting \| undefined` | The current sorting state. |
| `isLoading` | `boolean` | Whether the list is loading (includes both fetching and sorting). |
| `isLoadingMore` | `boolean` | Whether the list is loading more items via pagination. |
| `isSorting` | `boolean` | Whether a client-side sort is in progress. |
| `isEmpty` | `boolean` | Whether the list is empty. |
| `hasMore` | `boolean` | Whether there are more items to load. |
| `error` | `Error \| undefined` | The error instance returned by the last fetch. |
| `abort` | `VoidFunction` | Function to abort the current fetch. |
| `reload` | `VoidFunction` | Function to reload the list |
| `loadMore` | `VoidFunction` | Function to load more items |
| `setSorting` | `(sorting: Updater<Sorting \| undefined>) => void` | Function to update the sorting state and trigger a sort. Accepts a value or an updater function. |
| `setFilter` | `(filter: Updater<Filter>) => void` | Function to update the filter state and trigger a reload. Accepts a value or an updater function. |
| `clearFilter` | `VoidFunction` | Function to reset the filter to `initialFilter`. No-op if `initialFilter` was not provided. |
