Complete examples
Two end-to-end lists, one on the built-in cache and one on React Query, with nothing left out.
Complete example — without React Query (built-in cache)
A generic admin list using a Next.js server action, the native cache, and mutations:
// features/users/config.tsx
export const usersConfig = defineListConfig<User>({
id: 'users',
title: 'Users',
search: true,
pageSize: 20,
filters: [
{
id: 'filters',
filters: [
{
id: 'status',
field: 'status',
label: 'Status',
type: 'select',
options: [
{ value: 'active', label: 'Active' },
{ value: 'inactive', label: 'Inactive' },
],
columns: 2, // half-width, sits next to the next filter
},
{
id: 'role',
field: 'role',
label: 'Role',
type: 'select',
options: [
{ value: 'admin', label: 'Admin' },
{ value: 'editor', label: 'Editor' },
],
columns: 2,
},
{
id: 'createdAt',
field: 'createdAt',
label: 'Created',
type: 'date-range',
},
],
},
],
table: {
columns: [
{ key: 'name', header: 'Name' },
{ key: 'email', header: 'Email' },
{ key: 'status', header: 'Status' },
{
key: 'actions',
header: '',
render: item => <UserActions user={item} />,
},
],
},
})
// features/users/UserList.tsx
export function UserList() {
const adapter = serverActionAdapter<User>(async query => {
const { rows, total } = await listUsersAction(query)
return { data: rows, total }
})
return (
<ListView
config={usersConfig}
adapter={adapter}
staleTime={60_000} // built-in cache: keep responses for 1 minute
toolbarActions={[
{
label: 'New user',
onClick: () => openCreateModal(),
},
]}
/>
)
}
// features/users/UserActions.tsx
import { useListRefresh } from 'listkit'
function UserActions({ user }: { user: User }) {
const refresh = useListRefresh()
const handleDelete = async () => {
await deleteUserAction(user.id)
refresh() // invalidates the built-in cache and refetches
}
return <button onClick={handleDelete}>Delete</button>
}Complete example — with React Query
Same list, but letting TanStack Query own the cache and background refetch:
// features/users/UserList.tsx
import { useQuery } from '@tanstack/react-query'
import type { UseListDataHook } from 'listkit'
const useUsersListData: UseListDataHook<User> = (
adapter,
query,
refreshToken
) => {
const { data, isLoading, error } = useQuery({
queryKey: ['users', 'list', query, refreshToken],
queryFn: () => adapter.fetch(query),
staleTime: 5 * 60 * 1000,
})
return {
data: data?.data ?? [],
total: data?.total ?? 0,
isLoading,
error,
}
}
export function UserList() {
const adapter = serverActionAdapter<User>(async query => {
const { rows, total } = await listUsersAction(query)
return { data: rows, total }
})
return (
<ListView
config={usersConfig}
adapter={adapter}
useListData={useUsersListData} // React Query takes over
toolbarActions={[
{
label: 'New user',
onClick: () => openCreateModal(),
},
]}
/>
)
}