Skip to main content
View Zag.js on Github
Join the Discord server

Async List

The async list is used to display a list of items that are loaded asynchronously. Usually paired with the combobox or select component.

This was inspired by React Stately's useAsyncList hook.

Features

  • Support for pagination, sorting, and filtering
  • Support for abortable requests via AbortSignal
  • Manages loading and error states

Installation

To use the Async List machine in your project, run the following command in your command line:

npm install @zag-js/async-list @zag-js/react # or yarn add @zag-js/async-list @zag-js/react

Usage

First, import the async list package into your project

import * as asyncList from "@zag-js/async-list"

The async list package exports two key functions:

  • machine — The state machine logic for the async list widget.
  • connect — returns the properties and methods for the async data management.
import * as asyncList from "@zag-js/async-list" import { useMachine, normalizeProps } from "@zag-js/react" function AsyncList() { const service = useMachine(asyncList.machine, { async load({ signal }) { const res = await fetch(`/api/items`, { signal }) const json = await res.json() return { items: json.results, cursor: json.next, } }, }) const api = asyncList.connect(service, normalizeProps) return ( <div> <div> <pre>{JSON.stringify(api.items, null, 2)}</pre> <input type="text" onChange={(e) => api.setFilterText(e.target.value)} /> </div> <div> {api.loading && <p>Loading...</p>} <button onClick={() => api.reload()}>Reload</button> <button onClick={() => api.loadMore()}>Load More</button> <button onClick={() => api.sort({ column: "name", direction: "ascending" })} > Sort by name </button> </div> </div> ) }

Loading and Error States

The async list machine will automatically return the error and loading properties in the api when the load function returns an error or is loading.

  • api.error — The error instance returned by the last fetch.
  • api.loading — Whether the list is loading.
  • api.items — The items in the list after the last fetch.

Pagination

The async list supports paginated data to avoid loading too many items at once. This is accomplished by returning a cursor in addition to items from the load function.

When loadMore is called, the cursor is passed back to your load function, which you can use to determine the URL for the next page.

const service = useMachine(asyncList.machine, { async load({ signal, cursor }) { const requestUrl = cursor || "https://pokeapi.co/api/v2/pokemon" const res = await fetch(requestUrl, { signal }) const json = await res.json() return { items: json.results, cursor: json.next, } }, })

Then, you can use the api.loadMore method to load more items.

api.loadMore()

Reloading the data

Use the api.reload method to reload the data.

api.reload()

Sorting

The async list machine supports both client-side and server-side sorting. You can implement sorting by providing a sort function for client-side operations, or by handling the sortDescriptor parameter in your load function for server-side sorting.

Regardless of the sorting implementation, the way to trigger the sorting is by calling the api.sort method with the descriptor.

api.sort({ column: "name", direction: "ascending" })

Client-side sorting

Use the sort function to implement client-side sorting.

const service = useMachine(asyncList.machine, { async load({ signal }) { // ... }, sort({ items, sortDescriptor }) { return { items: items.sort((a, b) => { // Compare the items by the sorted column const aColumn = a[sortDescriptor.column] const bColumn = b[sortDescriptor.column] let direction = aColumn.localeCompare(bColumn) // Flip the direction if descending order is specified. if (sortDescriptor.direction === "descending") { direction *= -1 } return direction }), } }, })

Server-side sorting

For server-side sorting, use the sortDescriptor parameter in the load function to pass sorting parameters to your API.

const service = useMachine(asyncList.machine, { async load({ signal, sortDescriptor }) { let url = new URL("http://example.com/api") if (sortDescriptor) { url.searchParams.append("sort_key", sortDescriptor.column) url.searchParams.append("sort_direction", sortDescriptor.direction) } let res = await fetch(url, { signal }) let json = await res.json() return { items: json.results, } }, })

Filtering

Filtering your data list is often necessary, such as for user searches or query lookups. For server-side filtering, use the filterText parameter in the load function.

The way to trigger the filtering is by calling the api.setFilterText method with the filter text.

api.setFilterText("filter text")

The api.setFilterText method modifies the filterText and calls the load function to refresh the data with the updated filter.

const service = useMachine(asyncList.machine, { async load({ signal, filterText }) { let url = new URL("http://example.com/api") if (filterText) { url.searchParams.append("filter", filterText) } let res = await fetch(url, { signal }) let json = await res.json() return { items: json.results, } }, })

Aborting requests

Use the api.abort method to abort the current request.

api.abort()

This only works if you pass the signal parameter to the load function.

const service = useMachine(asyncList.machine, { async load({ signal }) { // ... }, })

Registering external dependencies

In even more complex scenarios, you may need to register external dependencies that trigger a reload of the data.

Use the dependencies parameter to register external dependencies. Ensure every dependency is a primitive value (no objects, arrays, maps, sets, etc.).

const service = useMachine(asyncList.machine, { dependencies: ["user"], async load({ signal, deps }) { // ... }, })

Methods and Properties

The async list's api exposes the following methods and properties:

Machine Context

The async list machine exposes the following context properties:

  • load(args: LoadDetails<C>) => Promise<LoadResult<T, C>>The function to call when the list is loaded
  • sort(args: SortDetails<T>) => Promise<{ items: T[]; }> | { items: T[]; }The function to call when the list is sorted
  • initialItemsT[]The initial items to display
  • initialSortDescriptorSortDescriptorThe initial sort descriptor to use
  • initialFilterTextstringThe initial filter text to use
  • dependenciesLoadDependency[]The dependencies to watch for changes
  • autoReloadbooleanWhether to automatically reload the list when the dependencies change
  • onSuccess(details: { items: T[]; }) => voidThe function to call when the list is loaded successfully
  • onError(details: { error: Error; }) => voidThe function to call when the list fails to load

Machine API

The async list api exposes the following methods:

  • itemsT[]The items in the list.
  • filterTextstringThe filter text.
  • cursorCThe cursor.
  • sortDescriptorSortDescriptorThe sort descriptor.
  • loadingbooleanWhether the list is loading.
  • erroranyThe error instance returned by the last fetch.
  • abort() => voidFunction to abort the current fetch.
  • reload() => voidFunction to reload the list
  • loadMore() => voidFunction to load more items
  • sort(sortDescriptor: SortDescriptor) => voidFunction to sort the list
  • setFilterText(filterText: string) => voidFunction to set the filter text

Edit this page on GitHub

A project by Chakra Systems
Copyright © 2025
On this page
Pro Support
and Training
Supercharge your team with support from the creators of Zag.js
Get Pro Support