Skip to content

Data Hooks

All data hooks are powered by TanStack Query v6 with automatic caching, background refetch, and optimistic updates.

All data hooks use <TData extends BaseRecord> generics for type-safe data access:

import type { BaseRecord } from '@svadmin/core';
interface Post extends BaseRecord {
id: number;
title: string;
status: 'draft' | 'published';
}
// TData is inferred as Post
const query = useList<Post>({ resource: 'posts' });

If you register a ResourceTypeMap, hooks auto-infer types and validate resource names at compile time.

All query hooks accept either static options or a getter function for reactive options:

// Static
useList({ resource: 'posts', filters: [{ field: 'status', operator: 'eq', value: 'published' }] });
// Reactive getter — re-fetches when $state changes
useList(() => ({ resource: 'posts', pagination, sorters, filters }));
const query = useList<Post>({ resource: 'posts', pagination: { current: 1, pageSize: 10 } });
// query.data → { data: Post[], total: number }
const query = useOne<Post>({ resource: 'posts', id: 1 });
// query.data → { data: Post }
const query = useShow<Post>({ resource: 'posts', id: 1 });
const query = useMany<Post>({ resource: 'posts', ids: [1, 2, 3] });
// query.data → { data: Post[] }
const query = useSelect({
resource: 'categories',
optionLabel: 'name',
optionValue: 'id',
defaultValue: [1, 2], // pre-selected values
searchField: 'name', // server-side search
debounce: 300, // search debounce (ms)
});
// query.options → [{ label: 'Tech', value: '1' }, ...]
// query.onSearch → (value: string) => void
const query = useInfiniteList<Post>({ resource: 'posts', pageSize: 20 });
// query.fetchNextPage() — loads the next page
// query.hasNextPage — boolean
const query = useCustom<{ stats: number[] }>({
url: '/api/dashboard/stats',
method: 'get',
});
// query.data → { data: { stats: number[] } }
const apiUrl = useApiUrl(); // → 'https://api.example.com'
const mutation = useCreate<Post>();
mutation.mutate({ resource: 'posts', variables: { title: 'Hello' } });
const mutation = useUpdate<Post>();
mutation.mutate({ resource: 'posts', id: 1, variables: { title: 'Updated' } });
const mutation = useDelete();
mutation.mutate({ resource: 'posts', id: 1 });
const mutation = useCustomMutation();
mutation.mutate({ url: '/api/posts/publish', method: 'post', values: { ids: [1, 2] } });

Bulk: useCreateMany, useUpdateMany, useDeleteMany

Section titled “Bulk: useCreateMany, useUpdateMany, useDeleteMany”

Same pattern with arrays of items.

Manually invalidate cached queries:

const invalidate = useInvalidate();
invalidate({ resource: 'posts', invalidates: ['list', 'one'] });

All mutation hooks support three modes via mutationMode:

ModeBehavior
pessimisticWait for server response (default)
optimisticUpdate UI immediately, rollback on error
undoableShow undo toast, delay server call by undoableTimeout ms
const mutation = useUpdate({ mutationMode: 'undoable', undoableTimeout: 5000 });