Skip to content

Form & Table Hooks

Full-featured form hook with auto-save, validation, and route auto-derivation.

const form = useForm({ resource: 'posts', action: 'create' });

On page /#/posts/edit/5, simply call:

const form = useForm();
// Automatically: resource='posts', action='edit', id='5'
OptionTypeDefaultDescription
resourcestring?autoResource name (auto from route)
action'create' | 'edit' | 'clone'autoForm action
idstring | numberautoRecord ID for edit/clone
redirect'list' | 'edit' | 'show' | false'list'Where to go after submit
validate(values) => errors | nullClient-side validation
autoSave{ enabled, debounce? }Auto-save on change
mutationModeMutationMode'pessimistic'Mutation strategy
undoableTimeoutnumber5000Undo timeout (ms)
const {
query, // TanStack query (edit/clone mode)
formLoading, // boolean (reactive getter)
mutation, // TanStack mutation
onFinish, // (values) => void — validates + submits
errors, // Record<string, string> (reactive getter)
setFieldError, // (field, message) => void
clearErrors, // () => void
clearFieldError, // (field) => void
triggerAutoSave, // (values) => void
autoSaveStatus, // 'idle' | 'saving' | 'saved' | 'error'
} = useForm();

When your DataProvider throws HttpError, validation errors are automatically mapped to form fields:

// In your DataProvider:
throw new HttpError('Validation Failed', 422, {
email: ['Email is required'],
name: 'Name is too short',
});
// → form.errors = { email: 'Email is required', name: 'Name is too short' }

Multi-step wizard form with step navigation and per-step validation.

<script lang="ts">
import { useStepsForm } from '@svadmin/core';
const {
steps: { currentStep, gotoStep, canGoNext, canGoPrev, nextStep, prevStep, totalSteps },
...formProps
} = useStepsForm({
resource: 'products',
action: 'create',
stepsCount: 3,
validate: (values, step) => {
if (step === 0 && !values.name) return { name: 'Required' };
if (step === 1 && !values.price) return { price: 'Required' };
return null;
},
});
</script>
{#if currentStep === 0}
<input bind:value={name} />
{:else if currentStep === 1}
<input bind:value={price} type="number" />
{:else}
<p>Review and submit</p>
{/if}
<button onclick={prevStep} disabled={!canGoPrev}>Back</button>
<button onclick={nextStep} disabled={!canGoNext}>Next</button>

Extends useForm return with a steps object:

PropertyTypeDescription
currentStepnumberCurrent step index (0-based)
totalStepsnumberTotal number of steps
gotoStep(step) => voidJump to a specific step
nextStep() => voidGo to next step (validates current)
prevStep() => voidGo to previous step
canGoNextbooleanWhether next step is available
canGoPrevbooleanWhether previous step is available

Form in a modal dialog — manages open/close state alongside form lifecycle.

const { modal, ...formProps } = useModalForm({ resource: 'posts' });
// modal.show(id?) — open modal (pass id for edit)
// modal.close() — close modal
// modal.visible — boolean
<button onclick={() => modal.show()}>Create Post</button>
<button onclick={() => modal.show(post.id)}>Edit</button>
{#if modal.visible}
<dialog open>
<form onsubmit|preventDefault={() => formProps.onFinish(values)}>
<!-- form fields -->
<button type="submit">Save</button>
<button type="button" onclick={modal.close}>Cancel</button>
</form>
</dialog>
{/if}

Identical API to useModalForm, designed for drawer/slide-out panels:

const { drawer, ...formProps } = useDrawerForm({ resource: 'posts' });
// drawer.show(id?) / drawer.close() / drawer.visible

Table hook with pagination, sorting, filtering, and URL sync.

const table = useTable({ resource: 'posts' });
const table = useTable(); // auto-derives resource from route
OptionTypeDefault
resourcestring?auto
paginationPagination{ current: 1, pageSize: 10 }
sortersSort[][]
filtersFilter[][]
syncWithLocationbooleanfalse
const {
query, // TanStack query result
pagination, // { current, pageSize } — reactive $state
sorters, // Sort[] — reactive $state
filters, // Filter[] — reactive $state
setCurrent, // (page) => void
setPageSize, // (size) => void
setSorters, // (sorters) => void
setFilters, // (filters) => void
} = useTable();