Single-page applications (SPA)
It’s possible to use the whole Superforms library on the client, for example in single page applications or when you want to call an external API instead of the SvelteKit form actions. A SPA is easy to create with SvelteKit, documented here.
Usage
const { form, enhance } = superForm(data, {
SPA: true
validators: false | ClientValidationAdapter<S>
})
By setting the SPA
option to true
, the form won’t be sent to the server when submitted. Instead, the client-side validators option will determine if the form is valid, and you can then use the onUpdate event as a submit handler, for example to call an external API with the form data.
Using +page.ts instead of +page.server.ts
Since SPA pages don’t have a server representation, you can use +page.ts to load initial data. Combined with a route parameter, we can make a CRUD-like page in a straightforward manner:
src/routes/user/[id]/+page.ts
import { error } from '@sveltejs/kit';
import { superValidate } from 'sveltekit-superforms';
import { zod } from 'sveltekit-superforms/adapters';
import { z } from 'zod';
export const _userSchema = z.object({
id: z.number().int().positive(),
name: z.string().min(2),
email: z.string().email()
});
export const load = async ({ params, fetch }) => {
const id = parseInt(params.id);
const request = await fetch(
`https://jsonplaceholder.typicode.com/users/${id}`
);
if (request.status >= 400) throw error(request.status);
const userData = await request.json();
const form = await superValidate(userData, zod(_userSchema));
return { form };
};
Displaying the form
We display the form in +page.svelte
like before, but with the SPA
option added, and the onUpdate
event now being used to validate the form data, instead of on the server:
src/routes/user/[id]/+page.svelte
<script lang="ts">
import { superForm, setMessage, setError } from 'sveltekit-superforms';
import { _userSchema } from './+page.js';
import { zod } from 'sveltekit-superforms/adapters';
let { data } = $props();
const { form, errors, message, constraints, enhance } = superForm(
data.form,
{
SPA: true,
validators: zod(_userSchema),
onUpdate({ form }) {
// Form validation
if (form.data.email.includes('spam')) {
setError(form, 'email', 'Suspicious email address.');
} else if (form.valid) {
// TODO: Call an external API with form.data, await the result and update form
setMessage(form, 'Valid data!');
}
}
}
);
</script>
<h1>Edit user</h1>
{#if $message}<h3>{$message}</h3>{/if}
<form method="POST" use:enhance>
<label>
Name<br />
<input
aria-invalid={$errors.name ? 'true' : undefined}
bind:value={$form.name}
{...$constraints.name} />
</label>
{#if $errors.name}<span class="invalid">{$errors.name}</span>{/if}
<label>
E-mail<br />
<input
type="email"
aria-invalid={$errors.email ? 'true' : undefined}
bind:value={$form.email}
{...$constraints.email} />
</label>
{#if $errors.email}<span class="invalid">{$errors.email}</span>{/if}
<button>Submit</button>
</form>
The validation in onUpdate
is almost the same as validating in a form action on the server. Nothing needs to be returned at the end since all modifications to the form
parameter in onUpdate
will update the form.
Without a +page.ts file
Since you can’t use top-level await in Svelte components, you can’t use superValidate
directly in +page.svelte
, as it is async. But if you want the default values only for the schema, you can import defaults
to avoid having a +page.ts
.
<script lang="ts">
import { superForm, defaults } from 'sveltekit-superforms';
import { zod } from 'sveltekit-superforms/adapters';
import { loginSchema } from '$lib/schemas';
const { form, errors, enhance } = superForm(defaults(zod(loginSchema)), {
SPA: true,
validators: zod(loginSchema),
onUpdate({ form }) {
if (form.valid) {
// TODO: Call an external API with form.data, await the result and update form
}
}
});
</script>
With initial top-level data
If you have initial data in the top level of the component, you can pass it as a first parameter to defaults
, but remember that it won’t be validated. There’s a trick though; if you want to show errors for the initial data, you can call validateForm
directly after superForm
. The validators
option must be set for this to work:
const initialData = { name: 'New user' };
const { form, errors, enhance, validateForm } = superForm(
defaults(initialData, zod(loginSchema)), {
SPA: true,
validators: zod(loginSchema)
// ...
}
);
validateForm({ update: true });