svelte-simple-form
Version:
A lightweight, **type-safe**, and **reactive** form state management hook for **Svelte 5**, featuring:
226 lines (168 loc) • 7.46 kB
Markdown
# 📝 Svelte Simple Form
A lightweight, **type-safe**, and **reactive** form state management hook for **Svelte 5**, featuring:
- Nested field paths support
- Validation integration with [Zod](https://github.com/colinhacks/zod)
- Dirty tracking, touched fields, and submission state
- Minimal dependencies & boilerplate — designed for **Svelte 5’s new reactive primitives**
---
## 🚀 Installation
```bash
npm install svelte-simple-form
```
Optionally use Zod for validation
```bash
npm install zod
```
**Note:** This hook is built to work seamlessly with **Svelte 5's reactive system**, using `$state`, `$effect`, and `tick`. Make sure your project is on Svelte 5 or later.
---
## 🎯 `useForm<T>(props: FormProps<T>)`
Creates and returns the reactive `form` object managing form state, validation, and events.
### Parameters
| Name | Type | Description |
| --------------- | ---------------------------------------------------------- | -------------------------------------- |
| `initialValues` | `T` Automatically | Initial values for the form fields. |
| `validation` | `{ zod: schema, relatedFields: Record<string, string[]> }` | Zod schema & related field validation. |
| `onSubmit` | Optional async callback | Called on successful submission. |
| `onChange` | Optional callback | Called on any field update. |
| `onReset` | Optional callback | Called when form resets. |
### Returns
```ts
{
form: {
initialValues: T;
data: T;
errors: Record<Path<T>, string[] | undefined>;
isValid: boolean;
isSubmitting: boolean;
isDirty: boolean;
touched: Record<Path<T>, boolean | undefined>;
setInitialValues(values: T, options?: { reset?: boolean }): void;
setIsDirty(dirty?: boolean): void;
setIsSubmitting(submitting?: boolean): void;
reset(): void;
resetField(field: Path<T>): void;
setError(field: Path<T>, error: string | string[]): void;
validate(field?: Path<T> | Path<T>[]): boolean;
submit(callback?: (data: T) => any): Promise<void>;
handler(node: HTMLFormElement): void;
}
}
```
---
## 🛠️ Methods & Usage Details
### `setInitialValues(values: T, options?: { reset?: boolean })`
- Set new initial values for the form.
- Optionally reset the current form data to the new initial values.
### `setIsDirty(dirty?: boolean)`
- Manually mark the form as dirty or clean.
### `setIsSubmitting(submitting?: boolean)`
- Manually set submitting state (e.g., show spinner).
### `reset()`
- Reset form data to initial values.
- Clear errors and touched fields.
- Calls `onReset` callback if provided.
### `resetField(field: Path<T>)`
- Reset a single field (and its nested children) to its initial value.
- Clears touched state for the reset field.
### `setError(field: Path<T>, error: string | string[])`
- Manually set an error for a specific field.
- Like server from check email really exit in db
### `validate(field?: Path<T> | Path<T>[])`
- Run validation on the entire form or specific fields using Zod.
- Clears errors on validated fields and sets new errors if any.
- Returns `true` if form is valid; `false` otherwise.
### `submit(callback?: (data: T) => any)`
- Perform validation (if configured).
- If valid, calls provided callback or `onSubmit`.
- Manages `isSubmitting` state during async submission.
### `handler(node: HTMLFormElement)`
- Attach a native submit event listener to a form element.
- Calls `submit()` automatically on submit event, preventing default browser submission.
---
## 💡 Reactive State (Bind these in your Svelte components)
| Property | Type | Description | |
| ------------------- | ---------------------------------------- | ---------------------------------------------- | --- |
| `form.data` | `T` | Current form data, bind inputs here. | |
| `form.errors` | `Record<Path<T>, string[] or undefined>` | Validation errors keyed by path. |
| `form.isValid` | `boolean` | True if form has no validation errors. | |
| `form.isSubmitting` | `boolean` | True if form is currently submitting. | |
| `form.isDirty` | `boolean` | True if form data differs from initial values. | |
| `form.touched` | `Record\<Path<T>, boolean or undefined>` | Tracks which fields have been modified. |
---
## 🧑💻 Example Usage in Svelte 5
```svelte
<script lang="ts">
import { useForm } from 'svelte-simple-form';
import { z } from 'zod';
let submitJson = $state('');
const schema = z.object({
name: z.string().min(1, 'Name is required'),
email: z.string().email("This isn't an email"),
age: z.number().min(18, 'Must be at least 18')
});
const { form } = useForm({
initialValues: { name: '', email: '', age: 0 },
validation: { zod: schema },
onSubmit: async (data) => {
submitJson = JSON.stringify(data);
console.log(`Submitted: ${JSON.stringify(data)}`);
},
onChange: (field, value) => {
submitJson = '';
console.log(`Field ${field} changed to`, value);
},
onReset: () => {
console.log('Form was reset');
}
});
function setEmailError() {
form.setError('email', 'Email really exit in db');
}
</script>
<div>
<form use:form.handler>
<!-- user name input -->
<div>
<input type="text" bind:value={form.data.name} placeholder="Name" />
{#if form.errors['name']?.length}
<p>{form.errors['name'][0]}</p>
{/if}
</div>
<!-- user email input -->
<div>
<input type="email" bind:value={form.data.email} placeholder="email" />
{#if form.errors['email']?.length}
<p>{form.errors['email'][0]}</p>
{/if}
</div>
<!-- form handler -->
<div>
<button type="submit" disabled={form.isSubmitting}>
{form.isSubmitting ? 'Submitting...' : 'Submit'}
</button>
<button type="button" onclick={() => form.reset()}> Reset </button>
<button type="button" onclick={() => setEmailError()}> setEmailError </button>
</div>
</form>
<div>
{#if submitJson}
<pre>
{submitJson}
</pre>
{/if}
</div>
</div>
```
---
## 💬 Tips & Notes
- Designed specifically for **Svelte 5**, leveraging its reactive primitives (`$state`, `$effect`, `tick`).
- Supports deeply nested objects and arrays with full type safety via `Path<T>`.
- Validation is optional but highly recommended using [Zod](https://github.com/colinhacks/zod).
- `onChange` is triggered for every changed field with path and new value.
- Use `form.isDirty` to track if the user has modified the form.
- `resetField` allows fine-grained reset of individual nested fields.
- `setError` allows manual setting of errors for specific fields.
- Use `form.handler` directive to bind submit event easily.
- Use `form.{state} = value` for manually change state value
- Use `form.{data|errors|touched}.{field} = value` for manually change state field value
- `initialValues` does not support nested string paths (like "body.height"), use objects instead.