Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SPA onUpdate() doesn't show errors set by setError() on subsequent submits #549

Open
1 task done
huyquanha opened this issue Jan 14, 2025 · 4 comments
Open
1 task done
Labels
documentation Improvements or additions to documentation

Comments

@huyquanha
Copy link

huyquanha commented Jan 14, 2025

  • 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>

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)

@huyquanha huyquanha added the bug Something isn't working label Jan 14, 2025
@huyquanha
Copy link
Author

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
Copy link
Owner

Are you on the latest SvelteKit? This has just been fixed: #536

@huyquanha
Copy link
Author

@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/>

image

@ciscoheat
Copy link
Owner

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.

@ciscoheat ciscoheat added documentation Improvements or additions to documentation and removed bug Something isn't working labels Jan 15, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Improvements or additions to documentation
Projects
None yet
Development

No branches or pull requests

2 participants