Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,43 @@ Alternatively, you could use `select` and `select multiple`:

> [!NOTE] As with unchecked `checkbox` inputs, if no selections are made then the data will be `undefined`. For this reason, the `languages` field uses `v.optional(v.array(...), [])` rather than just `v.array(...)`.

### Programmatic validation

In addition to declarative schema validation, you can programmatically mark fields as invalid inside the form handler using the `invalid` function. This is useful for cases where you can't know if something is valid until you try to perform some action:

```js
/// file: src/routes/shop/data.remote.js
import * as v from 'valibot';
import { form } from '$app/server';
import * as db from '$lib/server/database';

export const buyHotcakes = form(
v.object({
qty: v.pipe(
v.number(),
v.minValue(1, 'you must buy at least one hotcake')
)
}),
async (data, invalid) => {
try {
await db.buy(data.qty);
} catch (e) {
if (e.code === 'OUT_OF_STOCK') {
invalid(
invalid.qty(`we don't have enough hotcakes`)
);
}
}
}
);
```

The `invalid` function works as both a function and a proxy:

- Call `invalid(issue1, issue2, ...issueN)` to throw a validation error
- If an issue is a `string`, it applies to the form as a whole (and will show up in `fields.allIssues()`)
- Use `invalid.fieldName(message)` to create an issue for a specific field. Like `fields` this is type-safe and you can use regular property access syntax to create issues for deeply nested objects (e.g. `invalid.profile.email('Email already exists')` or `invalid.items[0].qty('Insufficient stock')`)

### Validation

If the submitted data doesn't pass the schema, the callback will not run. Instead, each invalid field's `issues()` method will return an array of `{ message: string }` objects, and the `aria-invalid` attribute (returned from `as(...)`) will be set to `true`:
Expand Down Expand Up @@ -756,6 +793,27 @@ await submit().updates(

The override will be applied immediately, and released when the submission completes (or fails).

### Multiple instances of a form

Some forms may be repeated as part of a list. In this case you can create separate instances of a form function via `for(id)` to achieve isolation.

```svelte
<!--- file: src/routes/todos/+page.svelte --->
<script>
import { getTodos, modifyTodo } from '../data.remote';
</script>

<h1>Todos</h1>

{#each await getTodos() as todo}
{@const modify = modifyTodo.for(todo.id)}
<form {...modify}>
<!-- -->
<button disabled={!!modify.pending}>save changes</button>
</form>
{/each}
```

### buttonProps

By default, submitting a form will send a request to the URL indicated by the `<form>` element's [`action`](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/form#attributes_for_form_submission) attribute, which in the case of a remote function is a property on the form object generated by SvelteKit.
Expand Down
26 changes: 23 additions & 3 deletions apps/svelte.dev/content/docs/kit/98-reference/10-@sveltejs-kit.md
Original file line number Diff line number Diff line change
Expand Up @@ -1253,6 +1253,26 @@ The content of the error.
</div>
</div></div>

## Invalid

A function and proxy object used to imperatively create validation errors in form handlers.

Call `invalid(issue1, issue2, ...issueN)` to throw a validation error.
If an issue is a `string`, it applies to the form as a whole (and will show up in `fields.allIssues()`)
Access properties to create field-specific issues: `invalid.fieldName('message')`.
The type structure mirrors the input data structure for type-safe field access.

<div class="ts-block">

```dts
type Invalid<Input = any> = ((
...issues: Array<string | StandardSchemaV1.Issue>
) => never) &
InvalidField<Input>;
```

</div>

## KitConfig

See the [configuration reference](/docs/kit/configuration) for details.
Expand Down Expand Up @@ -2315,8 +2335,8 @@ type RemoteForm<
[attachment: symbol]: (node: HTMLFormElement) => void;
};
/**
* Create an instance of the form for the given key.
* The key is stringified and used for deduplication to potentially reuse existing instances.
* Create an instance of the form for the given `id`.
* The `id` is stringified and used for deduplication to potentially reuse existing instances.
* Useful when you have multiple forms that use the same remote form action, for example in a loop.
* ```svelte
* {#each todos as todo}
Expand All @@ -2329,7 +2349,7 @@ type RemoteForm<
* ```
*/
for(
key: string | number | boolean
id: ExtractId<Input>
): Omit<RemoteForm<Input, Output>, 'for'>;
/** Preflight checks */
preflight(
Expand Down
14 changes: 11 additions & 3 deletions apps/svelte.dev/content/docs/kit/98-reference/20-$app-server.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,9 @@ See [Remote functions](/docs/kit/remote-functions#form) for full documentation.

```dts
function form<Output>(
fn: () => MaybePromise<Output>
fn: (
invalid: import('@sveltejs/kit').Invalid<void>
) => MaybePromise<Output>
): RemoteForm<void, Output>;
```

Expand All @@ -93,7 +95,10 @@ function form<Output>(
```dts
function form<Input extends RemoteFormInput, Output>(
validate: 'unchecked',
fn: (data: Input) => MaybePromise<Output>
fn: (
data: Input,
invalid: import('@sveltejs/kit').Invalid<Input>
) => MaybePromise<Output>
): RemoteForm<Input, Output>;
```

Expand All @@ -111,7 +116,10 @@ function form<
>(
validate: Schema,
fn: (
data: StandardSchemaV1.InferOutput<Schema>
data: StandardSchemaV1.InferOutput<Schema>,
invalid: import('@sveltejs/kit').Invalid<
StandardSchemaV1.InferOutput<Schema>
>
) => MaybePromise<Output>
): RemoteForm<StandardSchemaV1.InferInput<Schema>, Output>;
```
Expand Down