Async List
A hook for managing asynchronous data operations in list collections including loading, filtering, sorting, and pagination.
The useAsyncList
hook manages asynchronous data operations for list collections. It provides a comprehensive solution
for loading, filtering, sorting, and paginating data with built-in loading states, error handling, and request
cancellation.
import { useAsyncList } from '@ark-ui/react/collection'
const list = useAsyncList<User>({
async load({ signal }) {
const response = await fetch('/api/users', { signal })
const users = await response.json()
return { items: users }
},
})
console.log(list.items) // User[]
console.log(list.loading) // boolean
console.log(list.error) // Error | null
Examples
Basic Usage
The most basic usage involves providing a load
function that returns data.
import { useAsyncList } from '@ark-ui/react/collection'
interface Quote {
id: number
quote: string
author: string
}
export const Reload = () => {
const list = useAsyncList<Quote>({
async load() {
const response = await fetch(`https://dummyjson.com/quotes?limit=5&skip=${Math.floor(Math.random() * 50)}`)
if (!response.ok) {
throw new Error('Failed to fetch quotes')
}
const data = await response.json()
return { items: data.quotes }
},
})
return (
<div>
<div>
<button onClick={() => list.reload()} disabled={list.loading}>
{list.loading ? 'Loading...' : 'Reload Quotes'}
</button>
</div>
{list.error && <div>Error: {list.error.message}</div>}
<div>
{list.items.map((quote) => (
<div key={quote.id}>
<div>"{quote.quote}"</div>
<div>— {quote.author}</div>
</div>
))}
</div>
</div>
)
}
import { useAsyncList } from '@ark-ui/solid/collection'
import { For } from 'solid-js'
interface Quote {
id: number
quote: string
author: string
}
export const Reload = () => {
const list = useAsyncList<Quote>({
async load() {
const response = await fetch(`https://dummyjson.com/quotes?limit=5&skip=${Math.floor(Math.random() * 50)}`)
if (!response.ok) {
throw new Error('Failed to fetch quotes')
}
const data = await response.json()
return { items: data.quotes }
},
})
return (
<div>
<div>
<button onClick={() => list().reload()} disabled={list().loading}>
{list().loading ? 'Loading...' : 'Reload Quotes'}
</button>
</div>
{list().error && <div>Error: {list().error.message}</div>}
<div>
<For each={list().items}>
{(quote) => (
<div>
<div>"{quote.quote}"</div>
<div>— {quote.author}</div>
</div>
)}
</For>
</div>
</div>
)
}
<script setup lang="ts">
import { useAsyncList } from '@ark-ui/vue/collection'
interface Quote {
id: number
quote: string
author: string
}
const list = useAsyncList<Quote>({
async load() {
const response = await fetch(`https://dummyjson.com/quotes?limit=5&skip=${Math.floor(Math.random() * 50)}`)
if (!response.ok) {
throw new Error('Failed to fetch quotes')
}
const data = await response.json()
return { items: data.quotes }
},
})
</script>
<template>
<div>
<div>
<button @click="list.reload()" :disabled="list.loading">
{{ list.loading ? 'Loading...' : 'Reload Quotes' }}
</button>
</div>
<div v-if="list.error">Error: {{ list.error.message }}</div>
<div>
<div v-for="quote in list.items" :key="quote.id">
<div>"{{ quote.quote }}"</div>
<div>— {{ quote.author }}</div>
</div>
</div>
</div>
</template>
<script lang="ts">
import { useAsyncList } from '@ark-ui/svelte/collection'
interface Quote {
id: number
quote: string
author: string
}
const list = useAsyncList<Quote>({
async load() {
const response = await fetch(`https://dummyjson.com/quotes?limit=5&skip=${Math.floor(Math.random() * 50)}`)
if (!response.ok) {
throw new Error('Failed to fetch quotes')
}
const data = await response.json()
return { items: data.quotes }
},
})
</script>
<div>
<div>
<button onclick={() => list().reload()} disabled={list().loading}>
{list().loading ? 'Loading...' : 'Reload Quotes'}
</button>
</div>
{#if list().error}
<div>Error: {list().error.message}</div>
{/if}
<div>
{#each list().items as quote}
<div>
<div>"{quote.quote}"</div>
<div>— {quote.author}</div>
</div>
{/each}
</div>
</div>
Infinite Loading
Implement pagination by returning a cursor that indicates more data is available.
import { useAsyncList } from '@ark-ui/react/collection'
interface Post {
userId: number
id: number
title: string
body: string
}
export const InfiniteLoading = () => {
const list = useAsyncList<Post, number>({
autoReload: true,
async load({ cursor }) {
const page = cursor || 1
const limit = 10
const start = (page - 1) * limit
const response = await fetch(`https://jsonplaceholder.typicode.com/posts?_start=${start}&_limit=${limit}`)
if (!response.ok) {
throw new Error('Failed to fetch posts')
}
const posts: Post[] = await response.json()
const hasNextPage = posts.length === limit
return {
items: posts,
cursor: hasNextPage ? page + 1 : undefined,
}
},
})
return (
<div>
<div>
Loaded {list.items.length} posts
{list.cursor && ` (more available)`}
</div>
{list.cursor && (
<button onClick={() => list.loadMore()} disabled={list.loading}>
{list.loading ? 'Loading...' : 'Load More'}
</button>
)}
{list.error && <div>Error: {list.error.message}</div>}
<div>
{list.items.map((post, index) => (
<div key={post.id}>
<div>
<strong>{index + 1}: </strong>
<strong>{post.title}</strong>
</div>
<div>{post.body}</div>
</div>
))}
</div>
</div>
)
}
import { useAsyncList } from '@ark-ui/solid/collection'
import { For } from 'solid-js'
interface Post {
userId: number
id: number
title: string
body: string
}
export const InfiniteLoading = () => {
const list = useAsyncList<Post, number>({
autoReload: true,
async load({ cursor }) {
const page = cursor || 1
const limit = 10
const start = (page - 1) * limit
const response = await fetch(`https://jsonplaceholder.typicode.com/posts?_start=${start}&_limit=${limit}`)
if (!response.ok) {
throw new Error('Failed to fetch posts')
}
const posts: Post[] = await response.json()
const hasNextPage = posts.length === limit
return {
items: posts,
cursor: hasNextPage ? page + 1 : undefined,
}
},
})
return (
<div>
<div>
Loaded {list().items.length} posts
{list().cursor && ` (more available)`}
</div>
{list().cursor && (
<button onClick={() => list().loadMore()} disabled={list().loading}>
{list().loading ? 'Loading...' : 'Load More'}
</button>
)}
{list().error && <div>Error: {list().error.message}</div>}
<div>
<For each={list().items}>
{(post, index) => (
<div>
<div>
<strong>{index() + 1}: </strong>
<strong>{post.title}</strong>
</div>
<div>{post.body}</div>
</div>
)}
</For>
</div>
</div>
)
}
<script setup lang="ts">
import { useAsyncList } from '@ark-ui/vue/collection'
interface Post {
userId: number
id: number
title: string
body: string
}
const list = useAsyncList<Post, number>({
autoReload: true,
async load({ cursor }) {
const page = cursor || 1
const limit = 10
const start = (page - 1) * limit
const response = await fetch(`https://jsonplaceholder.typicode.com/posts?_start=${start}&_limit=${limit}`)
if (!response.ok) {
throw new Error('Failed to fetch posts')
}
const posts: Post[] = await response.json()
const hasNextPage = posts.length === limit
return {
items: posts,
cursor: hasNextPage ? page + 1 : undefined,
}
},
})
</script>
<template>
<div>
<div>
Loaded {{ list.items.length }} posts
<span v-if="list.cursor">(more available)</span>
</div>
<button v-if="list.cursor" @click="list.loadMore()" :disabled="list.loading">
{{ list.loading ? 'Loading...' : 'Load More' }}
</button>
<div v-if="list.error">Error: {{ list.error.message }}</div>
<div>
<div v-for="(post, index) in list.items" :key="post.id">
<div>
<strong>{{ index + 1 }}:</strong>
<strong>{{ post.title }}</strong>
</div>
<div>{{ post.body }}</div>
</div>
</div>
</div>
</template>
<script lang="ts">
import { useAsyncList } from '@ark-ui/svelte/collection'
interface Post {
userId: number
id: number
title: string
body: string
}
const list = useAsyncList<Post, number>({
autoReload: true,
async load({ cursor }) {
const page = cursor || 1
const limit = 10
const start = (page - 1) * limit
const response = await fetch(`https://jsonplaceholder.typicode.com/posts?_start=${start}&_limit=${limit}`)
if (!response.ok) {
throw new Error('Failed to fetch posts')
}
const posts: Post[] = await response.json()
const hasNextPage = posts.length === limit
return {
items: posts,
cursor: hasNextPage ? page + 1 : undefined,
}
},
})
</script>
<div>
<div>
Loaded {list().items.length} posts
{#if list().cursor}(more available){/if}
</div>
{#if list().cursor}
<button onclick={() => list().loadMore()} disabled={list().loading}>
{list().loading ? 'Loading...' : 'Load More'}
</button>
{/if}
{#if list().error}
<div>Error: {list().error.message}</div>
{/if}
<div>
{#each list().items as post, index}
<div>
<div>
<strong>{index + 1}: </strong>
<strong>{post.title}</strong>
</div>
<div>{post.body}</div>
</div>
{/each}
</div>
</div>
Filtering
Filter data based on user input with automatic debouncing and loading states.
import { useAsyncList } from '@ark-ui/react/collection'
interface User {
id: number
name: string
email: string
department: string
role: string
}
export const Filter = () => {
const list = useAsyncList<User>({
initialItems: mockUsers.slice(0, 5), // Show first 5 users initially
async load({ filterText }) {
await delay(500) // Simulate network delay
if (!filterText) {
return { items: mockUsers.slice(0, 5) }
}
const filtered = mockUsers.filter(
(user) =>
user.name.toLowerCase().includes(filterText.toLowerCase()) ||
user.email.toLowerCase().includes(filterText.toLowerCase()),
)
return { items: filtered }
},
})
return (
<div>
<div>
<input
type="text"
placeholder="Search users..."
value={list.filterText}
onChange={(e) => list.setFilterText(e.target.value)}
/>
{list.loading && <span>Loading...</span>}
</div>
{list.error && <div>Error: {list.error.message}</div>}
<div>
{list.items.map((user) => (
<div key={user.id}>
<div>
<strong>{user.name}</strong>
</div>
<div>{user.email}</div>
<div>
{user.department} • {user.role}
</div>
</div>
))}
</div>
{list.items.length === 0 && !list.loading && <div>No results found</div>}
</div>
)
}
const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
const mockUsers: User[] = [
{ id: 1, name: 'Alice Johnson', email: 'alice@example.com', department: 'Engineering', role: 'Senior Developer' },
{ id: 2, name: 'Bob Smith', email: 'bob@example.com', department: 'Marketing', role: 'Marketing Manager' },
{ id: 3, name: 'Carol Davis', email: 'carol@example.com', department: 'Engineering', role: 'Frontend Developer' },
{ id: 4, name: 'David Wilson', email: 'david@example.com', department: 'Sales', role: 'Sales Representative' },
{ id: 5, name: 'Eva Brown', email: 'eva@example.com', department: 'Engineering', role: 'DevOps Engineer' },
{ id: 6, name: 'Frank Miller', email: 'frank@example.com', department: 'Support', role: 'Customer Success' },
{ id: 7, name: 'Grace Lee', email: 'grace@example.com', department: 'Marketing', role: 'Content Creator' },
{ id: 8, name: 'Henry Taylor', email: 'henry@example.com', department: 'Engineering', role: 'Backend Developer' },
{ id: 9, name: 'Ivy Anderson', email: 'ivy@example.com', department: 'Sales', role: 'Account Manager' },
{ id: 10, name: 'Jack Thompson', email: 'jack@example.com', department: 'Support', role: 'Technical Support' },
{ id: 11, name: 'Kate Martinez', email: 'kate@example.com', department: 'Marketing', role: 'Brand Manager' },
{ id: 12, name: 'Liam Garcia', email: 'liam@example.com', department: 'Engineering', role: 'Full Stack Developer' },
{ id: 13, name: 'Mia Rodriguez', email: 'mia@example.com', department: 'Sales', role: 'Sales Director' },
{ id: 14, name: 'Noah Lopez', email: 'noah@example.com', department: 'Support', role: 'Support Manager' },
{ id: 15, name: 'Olivia White', email: 'olivia@example.com', department: 'Engineering', role: 'UI Designer' },
{ id: 16, name: 'Paul Harris', email: 'paul@example.com', department: 'Marketing', role: 'Digital Marketer' },
{ id: 17, name: 'Quinn Clark', email: 'quinn@example.com', department: 'Engineering', role: 'Mobile Developer' },
{ id: 18, name: 'Ruby Lewis', email: 'ruby@example.com', department: 'Sales', role: 'Business Development' },
{ id: 19, name: 'Sam Young', email: 'sam@example.com', department: 'Support', role: 'Documentation Specialist' },
{ id: 20, name: 'Tina Walker', email: 'tina@example.com', department: 'Marketing', role: 'Social Media Manager' },
]
import { useAsyncList } from '@ark-ui/solid/collection'
import { For } from 'solid-js'
interface User {
id: number
name: string
email: string
department: string
role: string
}
export const Filter = () => {
const list = useAsyncList<User>({
initialItems: mockUsers.slice(0, 5),
async load({ filterText }: { filterText?: string }) {
await delay(500)
if (!filterText) {
return { items: mockUsers.slice(0, 5) }
}
const filtered = mockUsers.filter(
(user) =>
user.name.toLowerCase().includes(filterText.toLowerCase()) ||
user.email.toLowerCase().includes(filterText.toLowerCase()),
)
return { items: filtered }
},
})
return (
<div>
<div>
<input
type="text"
placeholder="Search users..."
value={list().filterText}
onInput={(e) => list().setFilterText(e.target.value)}
/>
{list().loading && <span>Loading...</span>}
</div>
{list().error && <div>Error: {list().error.message}</div>}
<div>
<For each={list().items}>
{(user) => (
<div>
<div>
<strong>{user.name}</strong>
</div>
<div>{user.email}</div>
<div>
{user.department} • {user.role}
</div>
</div>
)}
</For>
</div>
{list().items.length === 0 && !list().loading && <div>No results found</div>}
</div>
)
}
const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
const mockUsers: User[] = [
{ id: 1, name: 'Alice Johnson', email: 'alice@example.com', department: 'Engineering', role: 'Senior Developer' },
{ id: 2, name: 'Bob Smith', email: 'bob@example.com', department: 'Marketing', role: 'Marketing Manager' },
{ id: 3, name: 'Carol Davis', email: 'carol@example.com', department: 'Engineering', role: 'Frontend Developer' },
{ id: 4, name: 'David Wilson', email: 'david@example.com', department: 'Sales', role: 'Sales Representative' },
{ id: 5, name: 'Eva Brown', email: 'eva@example.com', department: 'Engineering', role: 'DevOps Engineer' },
{ id: 6, name: 'Frank Miller', email: 'frank@example.com', department: 'Support', role: 'Customer Success' },
{ id: 7, name: 'Grace Lee', email: 'grace@example.com', department: 'Marketing', role: 'Content Creator' },
{ id: 8, name: 'Henry Taylor', email: 'henry@example.com', department: 'Engineering', role: 'Backend Developer' },
{ id: 9, name: 'Ivy Anderson', email: 'ivy@example.com', department: 'Sales', role: 'Account Manager' },
{ id: 10, name: 'Jack Thompson', email: 'jack@example.com', department: 'Support', role: 'Technical Support' },
{ id: 11, name: 'Kate Martinez', email: 'kate@example.com', department: 'Marketing', role: 'Brand Manager' },
{ id: 12, name: 'Liam Garcia', email: 'liam@example.com', department: 'Engineering', role: 'Full Stack Developer' },
{ id: 13, name: 'Mia Rodriguez', email: 'mia@example.com', department: 'Sales', role: 'Sales Director' },
{ id: 14, name: 'Noah Lopez', email: 'noah@example.com', department: 'Support', role: 'Support Manager' },
{ id: 15, name: 'Olivia White', email: 'olivia@example.com', department: 'Engineering', role: 'UI Designer' },
{ id: 16, name: 'Paul Harris', email: 'paul@example.com', department: 'Marketing', role: 'Digital Marketer' },
{ id: 17, name: 'Quinn Clark', email: 'quinn@example.com', department: 'Engineering', role: 'Mobile Developer' },
{ id: 18, name: 'Ruby Lewis', email: 'ruby@example.com', department: 'Sales', role: 'Business Development' },
{ id: 19, name: 'Sam Young', email: 'sam@example.com', department: 'Support', role: 'Documentation Specialist' },
{ id: 20, name: 'Tina Walker', email: 'tina@example.com', department: 'Marketing', role: 'Social Media Manager' },
]
<script setup lang="ts">
import { useAsyncList } from '@ark-ui/vue/collection'
interface User {
id: number
name: string
email: string
department: string
role: string
}
const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
const mockUsers: User[] = [
{ id: 1, name: 'Alice Johnson', email: 'alice@example.com', department: 'Engineering', role: 'Senior Developer' },
{ id: 2, name: 'Bob Smith', email: 'bob@example.com', department: 'Marketing', role: 'Marketing Manager' },
{ id: 3, name: 'Carol Davis', email: 'carol@example.com', department: 'Engineering', role: 'Frontend Developer' },
{ id: 4, name: 'David Wilson', email: 'david@example.com', department: 'Sales', role: 'Sales Representative' },
{ id: 5, name: 'Eva Brown', email: 'eva@example.com', department: 'Engineering', role: 'DevOps Engineer' },
{ id: 6, name: 'Frank Miller', email: 'frank@example.com', department: 'Support', role: 'Customer Success' },
{ id: 7, name: 'Grace Lee', email: 'grace@example.com', department: 'Marketing', role: 'Content Creator' },
{ id: 8, name: 'Henry Taylor', email: 'henry@example.com', department: 'Engineering', role: 'Backend Developer' },
{ id: 9, name: 'Ivy Anderson', email: 'ivy@example.com', department: 'Sales', role: 'Account Manager' },
{ id: 10, name: 'Jack Thompson', email: 'jack@example.com', department: 'Support', role: 'Technical Support' },
{ id: 11, name: 'Kate Martinez', email: 'kate@example.com', department: 'Marketing', role: 'Brand Manager' },
{ id: 12, name: 'Liam Garcia', email: 'liam@example.com', department: 'Engineering', role: 'Full Stack Developer' },
{ id: 13, name: 'Mia Rodriguez', email: 'mia@example.com', department: 'Sales', role: 'Sales Director' },
{ id: 14, name: 'Noah Lopez', email: 'noah@example.com', department: 'Support', role: 'Support Manager' },
{ id: 15, name: 'Olivia White', email: 'olivia@example.com', department: 'Engineering', role: 'UI Designer' },
{ id: 16, name: 'Paul Harris', email: 'paul@example.com', department: 'Marketing', role: 'Digital Marketer' },
{ id: 17, name: 'Quinn Clark', email: 'quinn@example.com', department: 'Engineering', role: 'Mobile Developer' },
{ id: 18, name: 'Ruby Lewis', email: 'ruby@example.com', department: 'Sales', role: 'Business Development' },
{ id: 19, name: 'Sam Young', email: 'sam@example.com', department: 'Support', role: 'Documentation Specialist' },
{ id: 20, name: 'Tina Walker', email: 'tina@example.com', department: 'Marketing', role: 'Social Media Manager' },
]
const list = useAsyncList<User>({
initialItems: mockUsers.slice(0, 5),
async load({ filterText }: { filterText?: string } = {}) {
await delay(500)
if (!filterText) {
return { items: mockUsers.slice(0, 5) }
}
const filtered = mockUsers.filter(
(user) =>
user.name.toLowerCase().includes(filterText.toLowerCase()) ||
user.email.toLowerCase().includes(filterText.toLowerCase()),
)
return { items: filtered }
},
})
</script>
<template>
<div>
<div>
<input
type="text"
placeholder="Search users..."
:value="list.filterText"
@input="list.setFilterText((($event.currentTarget as HTMLInputElement).value ?? '')"
/>
<span v-if="list.loading">Loading...</span>
</div>
<div v-if="list.error">Error: {{ list.error.message }}</div>
<div>
<div v-for="user in list.items" :key="user.id">
<div>
<strong>{{ user.name }}</strong>
</div>
<div>{{ user.email }}</div>
<div>{{ user.department }} • {{ user.role }}</div>
</div>
</div>
<div v-if="list.items.length === 0 && !list.loading">No results found</div>
</div>
</template>
<script lang="ts">
import { useAsyncList } from '@ark-ui/svelte/collection'
interface User {
id: number
name: string
email: string
department: string
role: string
}
const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
const mockUsers: User[] = [
{ id: 1, name: 'Alice Johnson', email: 'alice@example.com', department: 'Engineering', role: 'Senior Developer' },
{ id: 2, name: 'Bob Smith', email: 'bob@example.com', department: 'Marketing', role: 'Marketing Manager' },
{ id: 3, name: 'Carol Davis', email: 'carol@example.com', department: 'Engineering', role: 'Frontend Developer' },
{ id: 4, name: 'David Wilson', email: 'david@example.com', department: 'Sales', role: 'Sales Representative' },
{ id: 5, name: 'Eva Brown', email: 'eva@example.com', department: 'Engineering', role: 'DevOps Engineer' },
{ id: 6, name: 'Frank Miller', email: 'frank@example.com', department: 'Support', role: 'Customer Success' },
{ id: 7, name: 'Grace Lee', email: 'grace@example.com', department: 'Marketing', role: 'Content Creator' },
{ id: 8, name: 'Henry Taylor', email: 'henry@example.com', department: 'Engineering', role: 'Backend Developer' },
{ id: 9, name: 'Ivy Anderson', email: 'ivy@example.com', department: 'Sales', role: 'Account Manager' },
{ id: 10, name: 'Jack Thompson', email: 'jack@example.com', department: 'Support', role: 'Technical Support' },
{ id: 11, name: 'Kate Martinez', email: 'kate@example.com', department: 'Marketing', role: 'Brand Manager' },
{ id: 12, name: 'Liam Garcia', email: 'liam@example.com', department: 'Engineering', role: 'Full Stack Developer' },
{ id: 13, name: 'Mia Rodriguez', email: 'mia@example.com', department: 'Sales', role: 'Sales Director' },
{ id: 14, name: 'Noah Lopez', email: 'noah@example.com', department: 'Support', role: 'Support Manager' },
{ id: 15, name: 'Olivia White', email: 'olivia@example.com', department: 'Engineering', role: 'UI Designer' },
{ id: 16, name: 'Paul Harris', email: 'paul@example.com', department: 'Marketing', role: 'Digital Marketer' },
{ id: 17, name: 'Quinn Clark', email: 'quinn@example.com', department: 'Engineering', role: 'Mobile Developer' },
{ id: 18, name: 'Ruby Lewis', email: 'ruby@example.com', department: 'Sales', role: 'Business Development' },
{ id: 19, name: 'Sam Young', email: 'sam@example.com', department: 'Support', role: 'Documentation Specialist' },
{ id: 20, name: 'Tina Walker', email: 'tina@example.com', department: 'Marketing', role: 'Social Media Manager' },
]
const list = useAsyncList<User>({
initialItems: mockUsers.slice(0, 5),
async load({ filterText }: { filterText?: string } = {}) {
await delay(500)
if (!filterText) {
return { items: mockUsers.slice(0, 5) }
}
const filtered = mockUsers.filter(
(user) =>
user.name.toLowerCase().includes(filterText.toLowerCase()) ||
user.email.toLowerCase().includes(filterText.toLowerCase()),
)
return { items: filtered }
},
})
</script>
<div>
<div>
<input
type="text"
placeholder="Search users..."
value={list().filterText}
oninput={(e) => list().setFilterText(e.currentTarget.value)}
/>
{#if list().loading}
<span>Loading...</span>
{/if}
</div>
{#if list().error}
<div>Error: {list().error.message}</div>
{/if}
<div>
{#each list().items as user}
<div>
<div>
<strong>{user.name}</strong>
</div>
<div>{user.email}</div>
<div>
{user.department} • {user.role}
</div>
</div>
{/each}
</div>
{#if list().items.length === 0 && !list().loading}
<div>No results found</div>
{/if}
</div>
Sorting (Client-side)
Sort data on the client side after loading from the server.
import { useAsyncList } from '@ark-ui/react/collection'
import { useCollator } from '@ark-ui/react/locale'
interface User {
id: number
name: string
username: string
email: string
phone: string
website: string
}
export const SortClientSide = () => {
const collator = useCollator()
const list = useAsyncList<User>({
autoReload: true,
load: async () => {
const response = await fetch('https://jsonplaceholder.typicode.com/users')
const data = await response.json()
return { items: data }
},
sort({ items, descriptor }) {
return {
items: items.sort((a, b) => {
const { column, direction } = descriptor
let cmp = collator.compare(String(a[column]), String(b[column]))
if (direction === 'descending') {
cmp *= -1
}
return cmp
}),
}
},
})
const handleSort = (column: keyof User) => {
const currentSort = list.sortDescriptor
let direction: 'ascending' | 'descending' = 'ascending'
if (currentSort?.column === column && currentSort.direction === 'ascending') {
direction = 'descending'
}
list.sort({ column, direction })
}
const getSortIcon = (column: keyof User) => {
const current = list.sortDescriptor
if (current?.column !== column) return '↕️'
return current.direction === 'ascending' ? '↑' : '↓'
}
const descriptor = list.sortDescriptor
return (
<div>
<div>
{list.loading && <div>Loading...</div>}
{list.error && <div>Error: {list.error.message}</div>}
</div>
<div>Sorted by: {descriptor ? `${descriptor.column} (${descriptor.direction})` : 'none'}</div>
<table>
<thead>
<tr>
{[
{ key: 'name', label: 'Name' },
{ key: 'username', label: 'Username' },
{ key: 'email', label: 'Email' },
{ key: 'phone', label: 'Phone' },
{ key: 'website', label: 'Website' },
].map(({ key, label }) => (
<th key={key} onClick={() => handleSort(key as keyof User)}>
{label} {getSortIcon(key as keyof User)}
</th>
))}
</tr>
</thead>
<tbody>
{list.items.map((user) => (
<tr key={user.id}>
<td>{user.name}</td>
<td>{user.username}</td>
<td>{user.email}</td>
<td>{user.phone}</td>
<td>{user.website}</td>
</tr>
))}
</tbody>
</table>
</div>
)
}
import { useAsyncList } from '@ark-ui/solid/collection'
import { useCollator } from '@ark-ui/solid/locale'
import { For } from 'solid-js'
interface User {
id: number
name: string
username: string
email: string
phone: string
website: string
}
export const SortClientSide = () => {
const collator = useCollator()
const list = useAsyncList<User>({
autoReload: true,
load: async () => {
const response = await fetch('https://jsonplaceholder.typicode.com/users')
const data = await response.json()
return { items: data }
},
sort({ items, descriptor }) {
return {
items: items.sort((a, b) => {
const { column, direction } = descriptor
let cmp = collator().compare(String(a[column]), String(b[column]))
if (direction === 'descending') {
cmp *= -1
}
return cmp
}),
}
},
})
const handleSort = (column: keyof User) => {
const currentSort = list().sortDescriptor
let direction: 'ascending' | 'descending' = 'ascending'
if (currentSort?.column === column && currentSort.direction === 'ascending') {
direction = 'descending'
}
list().sort({ column, direction })
}
const getSortIcon = (column: keyof User) => {
const current = list().sortDescriptor
if (current?.column !== column) return '↕️'
return current.direction === 'ascending' ? '↑' : '↓'
}
const descriptor = () => list().sortDescriptor
return (
<div>
<div>
{list().loading && <div>Loading...</div>}
{list().error && <div>Error: {list().error.message}</div>}
</div>
<div>Sorted by: {descriptor() ? `${descriptor()?.column} (${descriptor()?.direction})` : 'none'}</div>
<table>
<thead>
<tr>
<For
each={[
{ key: 'name', label: 'Name' },
{ key: 'username', label: 'Username' },
{ key: 'email', label: 'Email' },
{ key: 'phone', label: 'Phone' },
{ key: 'website', label: 'Website' },
]}
>
{({ key, label }) => (
<th onClick={() => handleSort(key as keyof User)}>
{label} {getSortIcon(key as keyof User)}
</th>
)}
</For>
</tr>
</thead>
<tbody>
<For each={list().items}>
{(user) => (
<tr>
<td>{user.name}</td>
<td>{user.username}</td>
<td>{user.email}</td>
<td>{user.phone}</td>
<td>{user.website}</td>
</tr>
)}
</For>
</tbody>
</table>
</div>
)
}
<script setup lang="ts">
import { useAsyncList } from '@ark-ui/vue/collection'
import { useCollator } from '@ark-ui/vue/locale'
import { computed } from 'vue'
interface User {
id: number
name: string
username: string
email: string
phone: string
website: string
}
const collator = useCollator()
const list = useAsyncList<User>({
autoReload: true,
load: async () => {
const response = await fetch('https://jsonplaceholder.typicode.com/users')
const data = await response.json()
return { items: data }
},
sort({ items, descriptor }) {
return {
items: items.sort((a, b) => {
const { column, direction } = descriptor
let cmp = collator.value.compare(String(a[column]), String(b[column]))
if (direction === 'descending') {
cmp *= -1
}
return cmp
}),
}
},
})
const columns = [
{ key: 'name', label: 'Name' },
{ key: 'username', label: 'Username' },
{ key: 'email', label: 'Email' },
{ key: 'phone', label: 'Phone' },
{ key: 'website', label: 'Website' },
]
const handleSort = (column: keyof User) => {
const currentSort = list.value.sortDescriptor
let direction: 'ascending' | 'descending' = 'ascending'
if (currentSort?.column === column && currentSort.direction === 'ascending') {
direction = 'descending'
}
list.value.sort({ column, direction })
}
const getSortIcon = (column: keyof User) => {
const current = list.value.sortDescriptor
if (current?.column !== column) return '↕️'
return current.direction === 'ascending' ? '↑' : '↓'
}
const descriptor = computed(() => list.value.sortDescriptor)
</script>
<template>
<div>
<div>
<div v-if="list.loading">Loading...</div>
<div v-if="list.error">Error: {{ list.error.message }}</div>
</div>
<div>Sorted by: {{ descriptor ? `${descriptor.column} (${descriptor.direction})` : 'none' }}</div>
<table>
<thead>
<tr>
<th v-for="{ key, label } in columns" :key="key" @click="handleSort(key as keyof User)">
{{ label }} {{ getSortIcon(key as keyof User) }}
</th>
</tr>
</thead>
<tbody>
<tr v-for="user in list.items" :key="user.id">
<td>{{ user.name }}</td>
<td>{{ user.username }}</td>
<td>{{ user.email }}</td>
<td>{{ user.phone }}</td>
<td>{{ user.website }}</td>
</tr>
</tbody>
</table>
</div>
</template>
<script lang="ts">
import { useAsyncList } from '@ark-ui/svelte/collection'
import { useCollator } from '@ark-ui/svelte/locale'
interface User {
id: number
name: string
username: string
email: string
phone: string
website: string
}
const collator = useCollator()
const list = useAsyncList<User>({
autoReload: true,
load: async () => {
const response = await fetch('https://jsonplaceholder.typicode.com/users')
const data = await response.json()
return { items: data }
},
sort({ items, descriptor }) {
return {
items: items.sort((a, b) => {
const { column, direction } = descriptor
let cmp = collator().compare(String(a[column]), String(b[column]))
if (direction === 'descending') {
cmp *= -1
}
return cmp
}),
}
},
})
const handleSort = (column: keyof User) => {
const currentSort = list().sortDescriptor
let direction: 'ascending' | 'descending' = 'ascending'
if (currentSort?.column === column && currentSort.direction === 'ascending') {
direction = 'descending'
}
list().sort({ column, direction })
}
const getSortIcon = (column: keyof User) => {
const current = list().sortDescriptor
if (current?.column !== column) return '↕️'
return current.direction === 'ascending' ? '↑' : '↓'
}
const descriptor = $derived(list().sortDescriptor)
</script>
<div>
<div>
{#if list().loading}
<div>Loading...</div>
{/if}
{#if list().error}
<div>Error: {list().error.message}</div>
{/if}
</div>
<div>Sorted by: {descriptor ? `${descriptor.column} (${descriptor.direction})` : 'none'}</div>
<table>
<thead>
<tr>
{#each [{ key: 'name', label: 'Name' }, { key: 'username', label: 'Username' }, { key: 'email', label: 'Email' }, { key: 'phone', label: 'Phone' }, { key: 'website', label: 'Website' }] as { key, label }}
<th onclick={() => handleSort(key as keyof User)}>
{label}
{getSortIcon(key as keyof User)}
</th>
{/each}
</tr>
</thead>
<tbody>
{#each list().items as user}
<tr>
<td>{user.name}</td>
<td>{user.username}</td>
<td>{user.email}</td>
<td>{user.phone}</td>
<td>{user.website}</td>
</tr>
{/each}
</tbody>
</table>
</div>
Sorting (Server-side)
Send sort parameters to the server and reload data when sorting changes.
import { useAsyncList } from '@ark-ui/react/collection'
interface Product {
id: number
title: string
price: number
description: string
category: string
image: string
rating: {
rate: number
count: number
}
}
export const SortServerSide = () => {
const list = useAsyncList<Product>({
autoReload: true,
async load({ sortDescriptor }) {
const url = new URL('https://fakestoreapi.com/products')
if (sortDescriptor) {
const { direction } = sortDescriptor
url.searchParams.set('sort', direction === 'ascending' ? 'asc' : 'desc')
}
const response = await fetch(url)
const items = await response.json()
return { items }
},
})
const handleSort = (column: keyof Product) => {
const currentSort = list.sortDescriptor
let direction: 'ascending' | 'descending' = 'ascending'
if (currentSort?.column === column && currentSort.direction === 'ascending') {
direction = 'descending'
}
list.sort({ column, direction })
}
const getSortIcon = (column: keyof Product) => {
const desc = list.sortDescriptor
if (desc?.column !== column) return '↕️'
return desc.direction === 'ascending' ? '↑' : '↓'
}
return (
<div>
{list.loading && <div>Loading...</div>}
{list.error && <div>Error: {list.error.message}</div>}
<button onClick={() => handleSort('title')}>Sort title {getSortIcon('title')}</button>
<div>
{list.items.map((product) => (
<div key={product.id}>
<div>{product.title}</div>
<div>${product.price}</div>
<div>{product.category}</div>
<div>
{product.rating.rate} ({product.rating.count} reviews)
</div>
</div>
))}
</div>
{list.sortDescriptor && (
<div>
Sorted by {list.sortDescriptor.column} ({list.sortDescriptor.direction})
</div>
)}
</div>
)
}
import { useAsyncList } from '@ark-ui/solid/collection'
import { For, Show } from 'solid-js'
interface Product {
id: number
title: string
price: number
description: string
category: string
image: string
rating: {
rate: number
count: number
}
}
export const SortServerSide = () => {
const list = useAsyncList<Product>({
autoReload: true,
async load({ sortDescriptor }) {
const url = new URL('https://fakestoreapi.com/products')
if (sortDescriptor) {
const { direction } = sortDescriptor
url.searchParams.set('sort', direction === 'ascending' ? 'asc' : 'desc')
}
const response = await fetch(url)
const items = await response.json()
return { items }
},
})
const handleSort = (column: keyof Product) => {
const currentSort = list().sortDescriptor
let direction: 'ascending' | 'descending' = 'ascending'
if (currentSort?.column === column && currentSort.direction === 'ascending') {
direction = 'descending'
}
list().sort({ column, direction })
}
const getSortIcon = (column: keyof Product) => {
const desc = list().sortDescriptor
if (desc?.column !== column) return '↕️'
return desc.direction === 'ascending' ? '↑' : '↓'
}
return (
<div>
<Show when={list().loading}>
<div>Loading...</div>
</Show>
<Show when={list().error}>{(err) => <div>Error: {err().message}</div>}</Show>
<button onClick={() => handleSort('title')}>Sort title {getSortIcon('title')}</button>
<div>
<For each={list().items}>
{(product) => (
<div>
<div>{product.title}</div>
<div>${product.price}</div>
<div>{product.category}</div>
<div>
{product.rating.rate} ({product.rating.count} reviews)
</div>
</div>
)}
</For>
</div>
<Show when={list().sortDescriptor}>
{(desc) => (
<div>
Sorted by {desc().column} ({desc().direction})
</div>
)}
</Show>
</div>
)
}
<script setup lang="ts">
import { useAsyncList } from '@ark-ui/vue/collection'
interface Product {
id: number
title: string
price: number
description: string
category: string
image: string
rating: {
rate: number
count: number
}
}
const list = useAsyncList<Product>({
autoReload: true,
async load({ sortDescriptor }) {
const url = new URL('https://fakestoreapi.com/products')
if (sortDescriptor) {
const { direction } = sortDescriptor
url.searchParams.set('sort', direction === 'ascending' ? 'asc' : 'desc')
}
const response = await fetch(url)
const items = await response.json()
return { items }
},
})
const handleSort = (column: keyof Product) => {
const currentSort = list.value.sortDescriptor
let direction: 'ascending' | 'descending' = 'ascending'
if (currentSort?.column === column && currentSort.direction === 'ascending') {
direction = 'descending'
}
list.value.sort({ column, direction })
}
const getSortIcon = (column: keyof Product) => {
const desc = list.value.sortDescriptor
if (desc?.column !== column) return '↕️'
return desc.direction === 'ascending' ? '↑' : '↓'
}
</script>
<template>
<div>
<div v-if="list.loading">Loading...</div>
<div v-if="list.error">Error: {{ list.error.message }}</div>
<button @click="handleSort('title')">Sort title {{ getSortIcon('title') }}</button>
<div>
<div v-for="product in list.items" :key="product.id">
<div>{{ product.title }}</div>
<div>${{ product.price }}</div>
<div>{{ product.category }}</div>
<div>{{ product.rating.rate }} ({{ product.rating.count }} reviews)</div>
</div>
</div>
<div v-if="list.sortDescriptor">
Sorted by {{ list.sortDescriptor.column }} ({{ list.sortDescriptor.direction }})
</div>
</div>
</template>
<script lang="ts">
import { useAsyncList } from '@ark-ui/svelte/collection'
interface Product {
id: number
title: string
price: number
description: string
category: string
image: string
rating: {
rate: number
count: number
}
}
const list = useAsyncList<Product>({
autoReload: true,
async load({ sortDescriptor }) {
const url = new URL('https://fakestoreapi.com/products')
if (sortDescriptor) {
const { direction } = sortDescriptor
url.searchParams.set('sort', direction === 'ascending' ? 'asc' : 'desc')
}
const response = await fetch(url)
const items = await response.json()
return { items }
},
})
const handleSort = (column: keyof Product) => {
const currentSort = list().sortDescriptor
let direction: 'ascending' | 'descending' = 'ascending'
if (currentSort?.column === column && currentSort.direction === 'ascending') {
direction = 'descending'
}
list().sort({ column, direction })
}
const getSortIcon = (column: keyof Product) => {
const desc = list().sortDescriptor
if (desc?.column !== column) return '↕️'
return desc.direction === 'ascending' ? '↑' : '↓'
}
</script>
<div>
{#if list().loading}
<div>Loading...</div>
{/if}
{#if list().error}
<div>Error: {list().error.message}</div>
{/if}
<button onclick={() => handleSort('title')}>Sort title {getSortIcon('title')}</button>
<div>
{#each list().items as product}
<div>
<div>{product.title}</div>
<div>${product.price}</div>
<div>{product.category}</div>
<div>
{product.rating.rate} ({product.rating.count} reviews)
</div>
</div>
{/each}
</div>
{#if list().sortDescriptor}
<div>
Sorted by {list().sortDescriptor?.column} ({list().sortDescriptor?.direction})
</div>
{/if}
</div>
Dependencies
Automatically reload data when dependencies change, such as filter selections or external state.
import { useAsyncList } from '@ark-ui/react/collection'
import { useState } from 'react'
interface User {
id: number
name: string
email: string
department: string
role: string
}
export const Dependencies = () => {
const [selectedDepartment, setSelectedDepartment] = useState<string>('')
const [selectedRole, setSelectedRole] = useState<string>('')
const list = useAsyncList<User>({
initialItems: mockUsers, // Show all users initially
dependencies: [selectedDepartment, selectedRole],
async load({ filterText }) {
await delay(400) // Simulate network delay
let items = mockUsers
// Filter by department
if (selectedDepartment) {
items = items.filter((user) => user.department === selectedDepartment)
}
// Filter by role
if (selectedRole) {
items = items.filter((user) => user.role === selectedRole)
}
// Filter by search text
if (filterText) {
items = items.filter(
(user) =>
user.name.toLowerCase().includes(filterText.toLowerCase()) ||
user.email.toLowerCase().includes(filterText.toLowerCase()),
)
}
return { items }
},
})
return (
<div>
<h2>Dependencies Example</h2>
<div>
<select value={selectedDepartment} onChange={(e) => setSelectedDepartment(e.target.value)}>
<option value="">All Departments</option>
{departments.map((dept) => (
<option key={dept} value={dept}>
{dept}
</option>
))}
</select>
<select value={selectedRole} onChange={(e) => setSelectedRole(e.target.value)}>
<option value="">All Roles</option>
{roles.map((role) => (
<option key={role} value={role}>
{role}
</option>
))}
</select>
<input
type="text"
placeholder="Search..."
value={list.filterText}
onChange={(e) => list.setFilterText(e.target.value)}
/>
{list.loading && <span>Loading...</span>}
</div>
{list.error && <div>Error: {list.error.message}</div>}
<div>Found {list.items.length} users</div>
<div>
{list.items.map((user) => (
<div key={user.id}>
<div>
<strong>{user.name}</strong>
</div>
<div>{user.email}</div>
<div>
{user.department} • {user.role}
</div>
</div>
))}
</div>
{list.items.length === 0 && !list.loading && <div>No users found with current filters</div>}
</div>
)
}
// Helper function to simulate API delay
const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
const departments = ['Engineering', 'Marketing', 'Sales', 'Support']
const roles = [
'Senior Developer',
'Marketing Manager',
'Frontend Developer',
'Sales Representative',
'DevOps Engineer',
'Customer Success',
'Content Creator',
'Backend Developer',
'Account Manager',
'Technical Support',
'Brand Manager',
'Full Stack Developer',
'Sales Director',
'Support Manager',
'UI Designer',
'Digital Marketer',
'Mobile Developer',
'Business Development',
'Documentation Specialist',
'Social Media Manager',
]
const mockUsers: User[] = [
{ id: 1, name: 'Alice Johnson', email: 'alice@example.com', department: 'Engineering', role: 'Senior Developer' },
{ id: 2, name: 'Bob Smith', email: 'bob@example.com', department: 'Marketing', role: 'Marketing Manager' },
{ id: 3, name: 'Carol Davis', email: 'carol@example.com', department: 'Engineering', role: 'Frontend Developer' },
{ id: 4, name: 'David Wilson', email: 'david@example.com', department: 'Sales', role: 'Sales Representative' },
{ id: 5, name: 'Eva Brown', email: 'eva@example.com', department: 'Engineering', role: 'DevOps Engineer' },
{ id: 6, name: 'Frank Miller', email: 'frank@example.com', department: 'Support', role: 'Customer Success' },
{ id: 7, name: 'Grace Lee', email: 'grace@example.com', department: 'Marketing', role: 'Content Creator' },
{ id: 8, name: 'Henry Taylor', email: 'henry@example.com', department: 'Engineering', role: 'Backend Developer' },
{ id: 9, name: 'Ivy Anderson', email: 'ivy@example.com', department: 'Sales', role: 'Account Manager' },
{ id: 10, name: 'Jack Thompson', email: 'jack@example.com', department: 'Support', role: 'Technical Support' },
{ id: 11, name: 'Kate Martinez', email: 'kate@example.com', department: 'Marketing', role: 'Brand Manager' },
{ id: 12, name: 'Liam Garcia', email: 'liam@example.com', department: 'Engineering', role: 'Full Stack Developer' },
{ id: 13, name: 'Mia Rodriguez', email: 'mia@example.com', department: 'Sales', role: 'Sales Director' },
{ id: 14, name: 'Noah Lopez', email: 'noah@example.com', department: 'Support', role: 'Support Manager' },
{ id: 15, name: 'Olivia White', email: 'olivia@example.com', department: 'Engineering', role: 'UI Designer' },
{ id: 16, name: 'Paul Harris', email: 'paul@example.com', department: 'Marketing', role: 'Digital Marketer' },
{ id: 17, name: 'Quinn Clark', email: 'quinn@example.com', department: 'Engineering', role: 'Mobile Developer' },
{ id: 18, name: 'Ruby Lewis', email: 'ruby@example.com', department: 'Sales', role: 'Business Development' },
{ id: 19, name: 'Sam Young', email: 'sam@example.com', department: 'Support', role: 'Documentation Specialist' },
{ id: 20, name: 'Tina Walker', email: 'tina@example.com', department: 'Marketing', role: 'Social Media Manager' },
]
import { useAsyncList } from '@ark-ui/solid/collection'
import { createSignal, For } from 'solid-js'
interface User {
id: number
name: string
email: string
department: string
role: string
}
export const Dependencies = () => {
const [selectedDepartment, setSelectedDepartment] = createSignal<string>('')
const [selectedRole, setSelectedRole] = createSignal<string>('')
const list = useAsyncList<User>({
initialItems: mockUsers,
get dependencies() {
return [selectedDepartment(), selectedRole()]
},
async load({ filterText }: { filterText?: string }) {
await delay(400)
let items = mockUsers
if (selectedDepartment()) {
items = items.filter((user) => user.department === selectedDepartment())
}
if (selectedRole()) {
items = items.filter((user) => user.role === selectedRole())
}
if (filterText) {
items = items.filter(
(user) =>
user.name.toLowerCase().includes(filterText.toLowerCase()) ||
user.email.toLowerCase().includes(filterText.toLowerCase()),
)
}
return { items }
},
})
return (
<div>
<h2>Dependencies Example</h2>
<div>
<select value={selectedDepartment()} onChange={(e) => setSelectedDepartment(e.target.value)}>
<option value="">All Departments</option>
<For each={departments}>{(dept) => <option value={dept}>{dept}</option>}</For>
</select>
<select value={selectedRole()} onChange={(e) => setSelectedRole(e.target.value)}>
<option value="">All Roles</option>
<For each={roles}>{(role) => <option value={role}>{role}</option>}</For>
</select>
<input
type="text"
placeholder="Search..."
value={list().filterText}
onInput={(e) => list().setFilterText(e.target.value)}
/>
{list().loading && <span>Loading...</span>}
</div>
{list().error && <div>Error: {list().error.message}</div>}
<div>Found {list().items.length} users</div>
<div>
<For each={list().items}>
{(user) => (
<div>
<div>
<strong>{user.name}</strong>
</div>
<div>{user.email}</div>
<div>
{user.department} • {user.role}
</div>
</div>
)}
</For>
</div>
{list().items.length === 0 && !list().loading && <div>No users found with current filters</div>}
</div>
)
}
const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
const departments = ['Engineering', 'Marketing', 'Sales', 'Support']
const roles = [
'Senior Developer',
'Marketing Manager',
'Frontend Developer',
'Sales Representative',
'DevOps Engineer',
'Customer Success',
'Content Creator',
'Backend Developer',
'Account Manager',
'Technical Support',
'Brand Manager',
'Full Stack Developer',
'Sales Director',
'Support Manager',
'UI Designer',
'Digital Marketer',
'Mobile Developer',
'Business Development',
'Documentation Specialist',
'Social Media Manager',
]
const mockUsers: User[] = [
{ id: 1, name: 'Alice Johnson', email: 'alice@example.com', department: 'Engineering', role: 'Senior Developer' },
{ id: 2, name: 'Bob Smith', email: 'bob@example.com', department: 'Marketing', role: 'Marketing Manager' },
{ id: 3, name: 'Carol Davis', email: 'carol@example.com', department: 'Engineering', role: 'Frontend Developer' },
{ id: 4, name: 'David Wilson', email: 'david@example.com', department: 'Sales', role: 'Sales Representative' },
{ id: 5, name: 'Eva Brown', email: 'eva@example.com', department: 'Engineering', role: 'DevOps Engineer' },
{ id: 6, name: 'Frank Miller', email: 'frank@example.com', department: 'Support', role: 'Customer Success' },
{ id: 7, name: 'Grace Lee', email: 'grace@example.com', department: 'Marketing', role: 'Content Creator' },
{ id: 8, name: 'Henry Taylor', email: 'henry@example.com', department: 'Engineering', role: 'Backend Developer' },
{ id: 9, name: 'Ivy Anderson', email: 'ivy@example.com', department: 'Sales', role: 'Account Manager' },
{ id: 10, name: 'Jack Thompson', email: 'jack@example.com', department: 'Support', role: 'Technical Support' },
{ id: 11, name: 'Kate Martinez', email: 'kate@example.com', department: 'Marketing', role: 'Brand Manager' },
{ id: 12, name: 'Liam Garcia', email: 'liam@example.com', department: 'Engineering', role: 'Full Stack Developer' },
{ id: 13, name: 'Mia Rodriguez', email: 'mia@example.com', department: 'Sales', role: 'Sales Director' },
{ id: 14, name: 'Noah Lopez', email: 'noah@example.com', department: 'Support', role: 'Support Manager' },
{ id: 15, name: 'Olivia White', email: 'olivia@example.com', department: 'Engineering', role: 'UI Designer' },
{ id: 16, name: 'Paul Harris', email: 'paul@example.com', department: 'Marketing', role: 'Digital Marketer' },
{ id: 17, name: 'Quinn Clark', email: 'quinn@example.com', department: 'Engineering', role: 'Mobile Developer' },
{ id: 18, name: 'Ruby Lewis', email: 'ruby@example.com', department: 'Sales', role: 'Business Development' },
{ id: 19, name: 'Sam Young', email: 'sam@example.com', department: 'Support', role: 'Documentation Specialist' },
{ id: 20, name: 'Tina Walker', email: 'tina@example.com', department: 'Marketing', role: 'Social Media Manager' },
]
<script setup lang="ts">
import { useAsyncList } from '@ark-ui/vue/collection'
import { ref } from 'vue'
interface User {
id: number
name: string
email: string
department: string
role: string
}
const selectedDepartment = ref('')
const selectedRole = ref('')
const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
const departments = ['Engineering', 'Marketing', 'Sales', 'Support']
const roles = [
'Senior Developer',
'Marketing Manager',
'Frontend Developer',
'Sales Representative',
'DevOps Engineer',
'Customer Success',
'Content Creator',
'Backend Developer',
'Account Manager',
'Technical Support',
'Brand Manager',
'Full Stack Developer',
'Sales Director',
'Support Manager',
'UI Designer',
'Digital Marketer',
'Mobile Developer',
'Business Development',
'Documentation Specialist',
'Social Media Manager',
]
const mockUsers: User[] = [
{ id: 1, name: 'Alice Johnson', email: 'alice@example.com', department: 'Engineering', role: 'Senior Developer' },
{ id: 2, name: 'Bob Smith', email: 'bob@example.com', department: 'Marketing', role: 'Marketing Manager' },
{ id: 3, name: 'Carol Davis', email: 'carol@example.com', department: 'Engineering', role: 'Frontend Developer' },
{ id: 4, name: 'David Wilson', email: 'david@example.com', department: 'Sales', role: 'Sales Representative' },
{ id: 5, name: 'Eva Brown', email: 'eva@example.com', department: 'Engineering', role: 'DevOps Engineer' },
{ id: 6, name: 'Frank Miller', email: 'frank@example.com', department: 'Support', role: 'Customer Success' },
{ id: 7, name: 'Grace Lee', email: 'grace@example.com', department: 'Marketing', role: 'Content Creator' },
{ id: 8, name: 'Henry Taylor', email: 'henry@example.com', department: 'Engineering', role: 'Backend Developer' },
{ id: 9, name: 'Ivy Anderson', email: 'ivy@example.com', department: 'Sales', role: 'Account Manager' },
{ id: 10, name: 'Jack Thompson', email: 'jack@example.com', department: 'Support', role: 'Technical Support' },
{ id: 11, name: 'Kate Martinez', email: 'kate@example.com', department: 'Marketing', role: 'Brand Manager' },
{ id: 12, name: 'Liam Garcia', email: 'liam@example.com', department: 'Engineering', role: 'Full Stack Developer' },
{ id: 13, name: 'Mia Rodriguez', email: 'mia@example.com', department: 'Sales', role: 'Sales Director' },
{ id: 14, name: 'Noah Lopez', email: 'noah@example.com', department: 'Support', role: 'Support Manager' },
{ id: 15, name: 'Olivia White', email: 'olivia@example.com', department: 'Engineering', role: 'UI Designer' },
{ id: 16, name: 'Paul Harris', email: 'paul@example.com', department: 'Marketing', role: 'Digital Marketer' },
{ id: 17, name: 'Quinn Clark', email: 'quinn@example.com', department: 'Engineering', role: 'Mobile Developer' },
{ id: 18, name: 'Ruby Lewis', email: 'ruby@example.com', department: 'Sales', role: 'Business Development' },
{ id: 19, name: 'Sam Young', email: 'sam@example.com', department: 'Support', role: 'Documentation Specialist' },
{ id: 20, name: 'Tina Walker', email: 'tina@example.com', department: 'Marketing', role: 'Social Media Manager' },
]
const list = useAsyncList<User>({
initialItems: mockUsers,
get dependencies() {
return [selectedDepartment.value, selectedRole.value]
},
async load({ filterText }: { filterText?: string } = {}) {
await delay(400)
let items = mockUsers
if (selectedDepartment.value) {
items = items.filter((user) => user.department === selectedDepartment.value)
}
if (selectedRole.value) {
items = items.filter((user) => user.role === selectedRole.value)
}
if (filterText) {
items = items.filter(
(user) =>
user.name.toLowerCase().includes(filterText.toLowerCase()) ||
user.email.toLowerCase().includes(filterText.toLowerCase()),
)
}
return { items }
},
})
</script>
<template>
<div>
<h2>Dependencies Example</h2>
<div>
<select v-model="selectedDepartment">
<option value="">All Departments</option>
<option v-for="dept in departments" :key="dept" :value="dept">
{{ dept }}
</option>
</select>
<select v-model="selectedRole">
<option value="">All Roles</option>
<option v-for="role in roles" :key="role" :value="role">
{{ role }}
</option>
</select>
<input
type="text"
placeholder="Search..."
:value="list.filterText"
@input="list.setFilterText((($event.currentTarget as HTMLInputElement).value ?? '')"
/>
<span v-if="list.loading">Loading...</span>
</div>
<div v-if="list.error">Error: {{ list.error.message }}</div>
<div>Found {{ list.items.length }} users</div>
<div>
<div v-for="user in list.items" :key="user.id">
<div>
<strong>{{ user.name }}</strong>
</div>
<div>{{ user.email }}</div>
<div>{{ user.department }} • {{ user.role }}</div>
</div>
</div>
<div v-if="list.items.length === 0 && !list.loading">No users found with current filters</div>
</div>
</template>
<script lang="ts">
import { useAsyncList } from '@ark-ui/svelte/collection'
interface User {
id: number
name: string
email: string
department: string
role: string
}
const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
const departments = ['Engineering', 'Marketing', 'Sales', 'Support']
const roles = [
'Senior Developer',
'Marketing Manager',
'Frontend Developer',
'Sales Representative',
'DevOps Engineer',
'Customer Success',
'Content Creator',
'Backend Developer',
'Account Manager',
'Technical Support',
'Brand Manager',
'Full Stack Developer',
'Sales Director',
'Support Manager',
'UI Designer',
'Digital Marketer',
'Mobile Developer',
'Business Development',
'Documentation Specialist',
'Social Media Manager',
]
const mockUsers: User[] = [
{ id: 1, name: 'Alice Johnson', email: 'alice@example.com', department: 'Engineering', role: 'Senior Developer' },
{ id: 2, name: 'Bob Smith', email: 'bob@example.com', department: 'Marketing', role: 'Marketing Manager' },
{ id: 3, name: 'Carol Davis', email: 'carol@example.com', department: 'Engineering', role: 'Frontend Developer' },
{ id: 4, name: 'David Wilson', email: 'david@example.com', department: 'Sales', role: 'Sales Representative' },
{ id: 5, name: 'Eva Brown', email: 'eva@example.com', department: 'Engineering', role: 'DevOps Engineer' },
{ id: 6, name: 'Frank Miller', email: 'frank@example.com', department: 'Support', role: 'Customer Success' },
{ id: 7, name: 'Grace Lee', email: 'grace@example.com', department: 'Marketing', role: 'Content Creator' },
{ id: 8, name: 'Henry Taylor', email: 'henry@example.com', department: 'Engineering', role: 'Backend Developer' },
{ id: 9, name: 'Ivy Anderson', email: 'ivy@example.com', department: 'Sales', role: 'Account Manager' },
{ id: 10, name: 'Jack Thompson', email: 'jack@example.com', department: 'Support', role: 'Technical Support' },
{ id: 11, name: 'Kate Martinez', email: 'kate@example.com', department: 'Marketing', role: 'Brand Manager' },
{ id: 12, name: 'Liam Garcia', email: 'liam@example.com', department: 'Engineering', role: 'Full Stack Developer' },
{ id: 13, name: 'Mia Rodriguez', email: 'mia@example.com', department: 'Sales', role: 'Sales Director' },
{ id: 14, name: 'Noah Lopez', email: 'noah@example.com', department: 'Support', role: 'Support Manager' },
{ id: 15, name: 'Olivia White', email: 'olivia@example.com', department: 'Engineering', role: 'UI Designer' },
{ id: 16, name: 'Paul Harris', email: 'paul@example.com', department: 'Marketing', role: 'Digital Marketer' },
{ id: 17, name: 'Quinn Clark', email: 'quinn@example.com', department: 'Engineering', role: 'Mobile Developer' },
{ id: 18, name: 'Ruby Lewis', email: 'ruby@example.com', department: 'Sales', role: 'Business Development' },
{ id: 19, name: 'Sam Young', email: 'sam@example.com', department: 'Support', role: 'Documentation Specialist' },
{ id: 20, name: 'Tina Walker', email: 'tina@example.com', department: 'Marketing', role: 'Social Media Manager' },
]
let selectedDepartment = $state('')
let selectedRole = $state('')
const list = useAsyncList<User>({
initialItems: mockUsers,
get dependencies() {
return [selectedDepartment, selectedRole]
},
async load({ filterText }: { filterText?: string } = {}) {
await delay(400)
let items = mockUsers
if (selectedDepartment) {
items = items.filter((user) => user.department === selectedDepartment)
}
if (selectedRole) {
items = items.filter((user) => user.role === selectedRole)
}
if (filterText) {
items = items.filter(
(user) =>
user.name.toLowerCase().includes(filterText.toLowerCase()) ||
user.email.toLowerCase().includes(filterText.toLowerCase()),
)
}
return { items }
},
})
</script>
<div>
<div>
<select bind:value={selectedDepartment}>
<option value="">All Departments</option>
{#each departments as dept}
<option value={dept}>{dept}</option>
{/each}
</select>
<select bind:value={selectedRole}>
<option value="">All Roles</option>
{#each roles as role}
<option value={role}>{role}</option>
{/each}
</select>
<input
type="text"
placeholder="Search..."
value={list().filterText}
oninput={(e) => list().setFilterText(e.currentTarget.value)}
/>
{#if list().loading}
<span>Loading...</span>
{/if}
</div>
{#if list().error}
<div>Error: {list().error.message}</div>
{/if}
<div>Found {list().items.length} users</div>
<div>
{#each list().items as user}
<div>
<div>
<strong>{user.name}</strong>
</div>
<div>{user.email}</div>
<div>
{user.department} • {user.role}
</div>
</div>
{/each}
</div>
{#if list().items.length === 0 && !list().loading}
<div>No users found with current filters</div>
{/if}
</div>
API Reference
Props
- load (
(params: LoadParams<C>) => Promise<LoadResult<T, C>>
) - Function to load data asynchronously - sort (
(params: SortParams<T>) => Promise<SortResult<T>> | SortResult<T>
) - Optional function for client-side sorting - autoReload (
boolean
, default:false
) - Whether to automatically reload data on mount - initialItems (
T[]
, default:[]
) - Initial items to display before first load - dependencies (
any[]
, default:[]
) - Values that trigger a reload when changed - initialFilterText (
string
, default:''
) - Initial filter text value - initialSortDescriptor (
SortDescriptor | null
) - Initial sort configuration
Load Parameters
The load
function receives an object with the following properties:
- cursor (
C | undefined
) - Current cursor for pagination - filterText (
string
) - Current filter text - sortDescriptor (
SortDescriptor | null
) - Current sort configuration - signal (
AbortSignal
) - AbortController signal for request cancellation
Load Result
The load
function should return an object with:
- items (
T[]
) - The loaded items - cursor (
C | undefined
) - Optional cursor for next page
Sort Parameters
The sort
function receives an object with:
- items (
T[]
) - Current items to sort - descriptor (
SortDescriptor
) - Sort configuration withcolumn
anddirection
Return Value
The hook returns an object with the following properties and methods:
State Properties
- items (
T[]
) - Current list of items - loading (
boolean
) - Whether a load operation is in progress - error (
Error | null
) - Any error from the last operation - cursor (
C | undefined
) - Current cursor for pagination - filterText (
string
) - Current filter text - sortDescriptor (
SortDescriptor | null
) - Current sort configuration
Methods
- reload (
() => void
) - Reload data from the beginning - loadMore (
() => void
) - Load more items (when cursor is available) - setFilterText (
(text: string) => void
) - Set filter text and reload - sort (
(descriptor: SortDescriptor) => void
) - Apply sorting
Types
interface SortDescriptor {
column: string
direction: 'ascending' | 'descending'
}
interface LoadParams<C> {
cursor?: C
filterText: string
sortDescriptor?: SortDescriptor | null
signal: AbortSignal
}
interface LoadResult<T, C> {
items: T[]
cursor?: C
}