You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Before posting an issue, read the FAQ and search the previous issues.
Description
In SPA mode, the first time the form is submitted and onUpdate() is invoked, any additional errors added by setError() are successfully displayed.
When a field is further modified, the errors are gone (which is expected).
However, when the form is submitted a 2nd/3rd time..., and the form still contains errors such that setError() is called again in onUpdate(), the errors are no longer displayed.
Below is an example src/routes/login/+page.svelte that suffers from this. I can see the API call still being made, so onUpdate() is definitely still invoked on subsequent submits. Hence, the issue is more likely with setError()
<script lang="ts">
import * as Form from '$lib/components/ui/form';
import * as Card from '$lib/components/ui/card/index.js';
import { Input } from '$lib/components/ui/input/index.js';
import { superForm, defaults, setError } from 'sveltekit-superforms';
import { zod } from 'sveltekit-superforms/adapters';
import { loginFormSchema, type LoginFormSchema } from './schema';
import SuperDebug from 'sveltekit-superforms';
import { getProfileClient } from '$lib/connect/profile-client.svelte';
import { Code, ConnectError } from '@connectrpc/connect';
import { BadRequestSchema } from '$lib/protogen/google/rpc/error_details_pb';
import { goto } from '$app/navigation';
import { toast } from 'svelte-sonner';
import * as Alert from '$lib/components/ui/alert';
import CircleAlert from 'lucide-svelte/icons/circle-alert';
const profileClient = getProfileClient();
const form = superForm(defaults(zod(loginFormSchema)), {
SPA: true,
validators: zod(loginFormSchema),
onUpdate: async ({ form, result }) => {
if (form.valid) {
const { email, password } = form.data;
try {
await profileClient.login({
email,
password
});
toast.success('You have successfully logged in!');
// navigate the user to the home page with a flash message.
return goto('/');
} catch (err) {
result.type = 'failure';
const connectErr = ConnectError.from(err);
const errCode = connectErr.code;
// TODO: log errCode and errMsg in Sentry.
// const errMsg = connectErr.rawMessage;
switch (errCode) {
case Code.InvalidArgument: {
connectErr.findDetails(BadRequestSchema).find((i) => {
const violations = i.fieldViolations;
for (const violation of violations) {
const field = violation.field;
const message = violation.description;
let formField: '' | LoginFormSchema = '';
switch (field) {
case 'email':
case 'password':
formField = field;
break;
default:
break;
}
setError(form, formField, message);
}
result.status = 400;
});
break;
}
case Code.NotFound:
result.status = 404;
setError(form, 'No account found with that email address');
break;
case Code.Unauthenticated:
result.status = 401; // Unauthorized
setError(form, 'Email or password is incorrect');
break;
default:
result.status = 500;
setError(form, 'An unexpected error occurred when creating your account');
}
}
}
}
});
const { form: formData, errors, allErrors, enhance } = form;
let hasFormLevelErrors = $derived($errors._errors && $errors._errors.length > 0);
let hasFormErrors = $derived($allErrors.length > 0);
</script>
<SuperDebug data={$formData} />
<div class="flex min-h-screen flex-col">
{#if hasFormLevelErrors}
<Alert.Root variant="destructive" class="mx-auto mb-1 mt-auto max-w-sm">
<CircleAlert class="size-4" />
<Alert.Title>Error</Alert.Title>
<Alert.Description>
<ul>
{#each $errors._errors || [] as error}
<li>{error}</li>
{/each}
</ul>
</Alert.Description>
</Alert.Root>
{/if}
<Card.Root class="mx-auto {hasFormLevelErrors ? 'mb-auto mt-1' : 'my-auto'} max-w-sm">
<Card.Header>
<Card.Title class="text-xl">Login</Card.Title>
<Card.Description>Enter your email below to login to your account</Card.Description>
</Card.Header>
<Card.Content>
<div class="grid gap-4">
<form method="POST" use:enhance>
<Form.Field {form} name="email">
<Form.Control>
{#snippet children({ props })}
<Form.Label>Email</Form.Label>
<Input {...props} type="email" bind:value={$formData.email} />
{/snippet}
</Form.Control>
<Form.FieldErrors />
</Form.Field>
<Form.Field {form} name="password">
<Form.Control>
{#snippet children({ props })}
<Form.Label>Password</Form.Label>
<Input {...props} type="password" bind:value={$formData.password} />
{/snippet}
</Form.Control>
<Form.FieldErrors />
</Form.Field>
<Form.Button class="mt-4 w-full" disabled={hasFormErrors}>Log in</Form.Button>
</form>
<Form.Button variant="outline" class="w-full">Log in with Google</Form.Button>
</div>
<div class="mt-4 text-center text-sm">
Do not have an account?
<a href="/signup" class="underline"> Sign up </a>
</div>
</Card.Content>
</Card.Root>
</div>
Side note, it seems like result.type = 'failure' is necessary for the errors to be displayed, but it's not mentioned in the documentation.
Also, setError status option (to change the status code) doesn't seem to take any effect. I have to set result.status = ... explicitly (as seen in the example above). However, none of these are blockers, just thought you should know
@ciscoheat thanks! updating to 2.15.2 indeed fixed it. And it looks like I no longer need to set result.type = 'failure' anymore which's great.
However, setError() status option still doesn't seem like it's working as expected. It should be 404 here instead of 200. To make it 404, I still have to write result.status = 404 explicitly.
I don't think this has big impact to SPA though, just a bit confusing if someone is debugging things with <SuperDebug/>
Yes, form and result are kind of disconnected in the onUpdate event, so result takes precedence. I'll add a note that you cannot use status in setMessage for SPA.
Description
In SPA mode, the first time the form is submitted and
onUpdate()
is invoked, any additional errors added bysetError()
are successfully displayed.When a field is further modified, the errors are gone (which is expected).
However, when the form is submitted a 2nd/3rd time..., and the form still contains errors such that
setError()
is called again inonUpdate()
, the errors are no longer displayed.Below is an example
src/routes/login/+page.svelte
that suffers from this. I can see the API call still being made, soonUpdate()
is definitely still invoked on subsequent submits. Hence, the issue is more likely withsetError()
If applicable, a MRE
Use this template project to create a minimal reproducible example that you can link to here: https://sveltelab.dev/github.com/ciscoheat/superforms-examples/tree/zod (right click to open in a new tab)
The text was updated successfully, but these errors were encountered: