{"slug":"async-list","title":"Async List","description":"Using the Async List machine in your project.","contentType":"utilities","content":"The async list is used to display a list of items that are loaded\nasynchronously. Usually paired with the combobox or select component.\n\n> This was inspired by React Stately's\n> [useAsyncList](https://react-spectrum.adobe.com/react-stately/useAsyncList.html)\n> hook.\n\n<Resources pkg=\"@zag-js/async-list\" />\n\n**Features**\n\n- Support for pagination, sorting, and filtering\n- Support for abortable requests via `AbortSignal`\n- Manages loading and error states\n\n## Installation\n\nTo use the Async List machine in your project, run the following command in your\ncommand line:\n\n<CodeSnippet id=\"async-list/installation.mdx\" />\n\n## Usage\n\nFirst, import the async list package into your project\n\n```jsx\nimport * as asyncList from \"@zag-js/async-list\"\n```\n\nThe async list package exports two key functions:\n\n- `machine` — The state machine logic for the async list widget.\n- `connect` — returns the properties and methods for the async data management.\n\n<CodeSnippet id=\"async-list/usage.mdx\" />\n\n### Loading and Error States\n\nThe async list machine will automatically return the `error` and `loading`\nproperties in the `api` when the `load` function returns an error or is loading.\n\n- `api.error` — The error instance returned by the last fetch.\n- `api.isLoading` — Whether the list is loading (initial load, reload, filter,\n  or sort).\n- `api.isLoadingMore` — Whether more items are being loaded via pagination.\n- `api.isEmpty` — Whether the list has no items.\n- `api.items` — The items in the list after the last fetch.\n\n### Pagination\n\nThe async list supports paginated data to avoid loading too many items at once.\nThis is accomplished by returning a cursor in addition to items from the `load`\nfunction.\n\nWhen `loadMore` is called, the cursor is passed back to your `load` function,\nwhich you can use to determine the URL for the next page.\n\n```tsx\nconst service = useMachine(asyncList.machine, {\n  async load({ signal, cursor }) {\n    const requestUrl = cursor || \"https://pokeapi.co/api/v2/pokemon\"\n    const res = await fetch(requestUrl, { signal })\n    const json = await res.json()\n\n    return {\n      items: json.results,\n      cursor: json.next,\n    }\n  },\n})\n```\n\nThen, you can use the `api.loadMore` method to load more items.\n\n```tsx\napi.loadMore()\n```\n\n### Reloading the data\n\nUse the `api.reload` method to reload the data.\n\n```tsx\napi.reload()\n```\n\n### Sorting\n\nThe async list machine supports both client-side and server-side sorting. You\ncan implement sorting by providing a `sort` function for client-side operations,\nor by handling the `sorting` parameter in your `load` function for server-side\nsorting.\n\nRegardless of the sorting implementation, the way to trigger the sorting is by\ncalling the `api.setSorting` method with the descriptor.\n\n```tsx\napi.setSorting({ column: \"name\", direction: \"ascending\" })\n```\n\n#### Client-side sorting\n\nUse the `sort` function to implement client-side sorting.\n\n```tsx\nconst service = useMachine(asyncList.machine, {\n  async load({ signal }) {\n    // ...\n  },\n  sort({ items, sorting }) {\n    return {\n      items: items.sort((a, b) => {\n        // Compare the items by the sorted column\n        const aColumn = a[sorting.column]\n        const bColumn = b[sorting.column]\n        let direction = aColumn.localeCompare(bColumn)\n\n        // Flip the direction if descending order is specified.\n        if (sorting.direction === \"descending\") {\n          direction *= -1\n        }\n\n        return direction\n      }),\n    }\n  },\n})\n```\n\n#### Server-side sorting\n\nFor server-side sorting, use the `sorting` parameter in the `load` function to\npass sorting parameters to your API.\n\n```tsx\nconst service = useMachine(asyncList.machine, {\n  async load({ signal, sorting }) {\n    let url = new URL(\"http://example.com/api\")\n    if (sorting) {\n      url.searchParams.append(\"sort_key\", sorting.column)\n      url.searchParams.append(\"sort_direction\", sorting.direction)\n    }\n\n    let res = await fetch(url, { signal })\n    let json = await res.json()\n    return {\n      items: json.results,\n    }\n  },\n})\n```\n\n### Filtering\n\nFiltering your data list is often necessary, such as for user searches or query\nlookups. For server-side filtering, use the `filter` parameter in the `load`\nfunction.\n\nThe way to trigger the filtering is by calling the `api.setFilter` method with\nthe filter value.\n\n```tsx\napi.setFilter(\"filter text\")\n```\n\nThe `api.setFilter` method modifies the `filter` and calls the `load` function\nto refresh the data with the updated filter.\n\n```tsx\nconst service = useMachine(asyncList.machine, {\n  async load({ signal, filter }) {\n    let url = new URL(\"http://example.com/api\")\n    if (filter) {\n      url.searchParams.append(\"filter\", filter)\n    }\n\n    let res = await fetch(url, { signal })\n    let json = await res.json()\n    return {\n      items: json.results,\n    }\n  },\n})\n```\n\n### Aborting requests\n\nUse the `api.abort` method to abort the current request.\n\n```tsx\napi.abort()\n```\n\nThis only works if you pass the `signal` parameter to the `load` function.\n\n```tsx\nconst service = useMachine(asyncList.machine, {\n  async load({ signal }) {\n    // ...\n  },\n})\n```\n\n### Registering external dependencies\n\nIn even more complex scenarios, you may need to register external dependencies\nthat trigger a reload of the data.\n\nUse the `dependencies` parameter to register external dependencies. Ensure every\ndependency is a primitive value (no objects, arrays, maps, sets, etc.).\n\n```tsx\nconst service = useMachine(asyncList.machine, {\n  dependencies: [userId],\n  async load({ signal }) {\n    // read the external dependency directly from your scope\n    const res = await fetch(`/api/users/${userId}/items`, { signal })\n    return { items: await res.json() }\n  },\n})\n```\n\n## Composing with a collection\n\nAsync list is a data loader. It handles fetching, pagination, sorting, and\nfiltering, but it doesn't own the list structure. To render items in a\n`combobox`, `select`, or `listbox`, pair it with a collection. Async list owns\nthe server data, and the collection owns the UI structure and any client-side\nmanipulation.\n\n```tsx\nimport * as asyncList from \"@zag-js/async-list\"\nimport * as combobox from \"@zag-js/combobox\"\nimport { normalizeProps, useMachine } from \"@zag-js/react\"\nimport { useMemo } from \"react\"\n\nfunction ComboboxWithAsyncData() {\n  // 1. Load server data with async list (filtering happens on the server)\n  const list = useMachine(asyncList.machine, {\n    async load({ signal, filter }) {\n      const res = await fetch(`/api/items?q=${filter ?? \"\"}`, { signal })\n      return { items: await res.json() }\n    },\n  })\n  const listApi = asyncList.connect(list)\n\n  // 2. Build a collection from the loaded items for the combobox to render\n  const collection = useMemo(\n    () => combobox.collection({ items: listApi.items }),\n    [listApi.items],\n  )\n\n  // 3. Drive server-side filtering from the combobox input\n  const cb = useMachine(combobox.machine, {\n    collection,\n    onInputValueChange({ inputValue }) {\n      listApi.setFilter(inputValue)\n    },\n  })\n\n  // ...render the combobox\n}\n```\n\n### Mutating items\n\nCollection mutation methods (`insert`, `append`, `prepend`, `remove`, `move`,\n`update`, `reorder`) are immutable and return a new collection. Keep the\ncollection in state, seed it from the loaded items, and apply optimistic updates\nwithout refetching.\n\n```tsx\nconst [collection, setCollection] = useState(() =>\n  combobox.collection({ items: [] }),\n)\n\n// keep the collection in sync with freshly loaded data\nuseEffect(() => {\n  setCollection(combobox.collection({ items: listApi.items }))\n}, [listApi.items])\n\n// optimistic mutations\nconst removeItem = (value: string) => setCollection((c) => c.remove(value))\nconst appendItem = (item: Item) => setCollection((c) => c.append(item))\n```\n\n## Methods and Properties\n\nThe async list's `api` exposes the following methods and properties:\n\n### Machine Context\n\nThe async list machine exposes the following context properties:\n\n<ContextTable name=\"async-list\" />\n\n### Machine API\n\nThe async list `api` exposes the following methods:\n\n<ApiTable name=\"async-list\" />","package":"@zag-js/async-list","editUrl":"https://github.com/chakra-ui/zag/edit/main/website/data/utilities/async-list.mdx"}