I’m building a SvelteKit application where users can edit student details on a dynamic route (/student/[id]). I’m using Supabase for the database and SvelteKit Superforms to handle the form.
The editing functionality works correctly. However, I’m facing a strange issue with data consistency only on page reloads.
The Problem
When I navigate from my student list page (/students) to a specific student’s detail page (/student/[id]), the data is fetched and the form is populated correctly.
The issue occurs when I reload the page directly on a detail page (e.g., /student/123-abc-456).
Observed behavior on reload:
The correct student data is fetched from the load function and displayed on the page for a brief moment.
After a split second, the data within the form fields (managed by superForm) is replaced by the data of another student. I could confirm this with the SuperDebug Component
The non-form data on the page (like the header ) which uses data.student remains correct. This suggests the issue is specific to the Superforms store ($form).
I’ve confirmed through logging that the data.student object passed from +page.server.ts is always correct. The issue seems to be with how the $form store is being hydrated or updated on the client side after a full page load.
What could be causing the $form store to be updated with incorrect data after a page reload, and how can I ensure it stays in sync with the data loaded for the current page?
+page.server.ts
import { supabase } from '$lib/supabaseClient';
import type { PageServerLoad } from './$types';
import type { Actions } from '@sveltejs/kit';
import { zod } from 'sveltekit-superforms/adapters';
import { superValidate } from 'sveltekit-superforms';
import { fail } from '@sveltejs/kit';
import type { Student } from '$lib/types';
import z from 'zod';
const studentSchema = z.object({
id: z.string().uuid(),
first_name: z.string().min(2, 'Vorname ist erforderlich.'),
last_name: z.string().min(2, 'Nachname ist erforderlich.'),
email: z.string().email('Ungültige E-Mail-Adresse.').nullable(),
phone_number: z.string().nullable()
});
// This is responsible for loading the data from the database
export const load: PageServerLoad = async (event) => {
const id = event.params.id;
const { data: student, error } = await supabase
.from('students')
.select('*')
.eq('id', id)
.single<Student>();
if (error || !student) {
console.error('Error loading student:', error?.message);
}
const form = await superValidate(student, zod(studentSchema));
return { student, form };
};
// This is responsible for the form submission
export const actions: Actions = {
default: async ({ request }) => {
const form = await superValidate(request, zod(studentSchema));
if (!form.valid) {
return fail(400, { form });
}
const { error } = await supabase.from('students').update(form.data).eq('id', form.data.id);
if (error) {
console.error('Error updating student:', error.message);
return fail(400, { form });
}
return { form };
}
};
+page.svelte
<script lang="ts">
import Button from '$lib/components/ui/button/button.svelte';
import * as Tabs from '$lib/components/ui/tabs';
import * as Card from '$lib/components/ui/card';
import type { PageData } from './$types';
import ChevronLeft from '@lucide/svelte/icons/chevron-left';
import SuperDebug, { superForm } from 'sveltekit-superforms';
import { Badge } from '$lib/components/ui/badge';
import Mail from '@lucide/svelte/icons/mail';
import Phone from '@lucide/svelte/icons/phone';
import Pencil from '@lucide/svelte/icons/pencil';
import FormButton from '$lib/components/ui/form/form-button.svelte';
import Input from '$lib/components/ui/input/input.svelte';
export let data: PageData;
const { form, errors, enhance } = superForm(data.form);
console.log($form);
</script>
{#if data.student}
{#key data.student.id}
<SuperDebug data={$form} />
<header class="border-text-muted-foreground border-b">
<h1 class="mt-4 mb-2 text-2xl font-bold">
<Button variant="ghost" href="/students">
<ChevronLeft size="18" />
</Button>
Schülerprofil von {data.student.first_name}
{data.student.last_name}
</h1>
</header>
<div class="mt-8 flex flex-col gap-y-4 lg:flex-row lg:gap-x-4">
{#if data.student}
<Tabs.Root value="overview" class="w-full">
<Tabs.List>
<Tabs.Trigger value="overview">Übersicht</Tabs.Trigger>
<Tabs.Trigger value="password">Password</Tabs.Trigger>
</Tabs.List>
<Tabs.Content value="overview">
<div class="grid gap-4 xl:grid-cols-3">
<div class="space-y-4 xl:col-span-1">
<Card.Root>
<Card.Header>
<Card.Title class="relative flex flex-col items-center gap-2">
<div class="circle-avatar">
<img
alt="Student's avatar"
src="https://images.unsplash.com/photo-1517365830460-955ce3ccd263?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=384&q=80"
class="h-24 w-24 rounded-full"
/>
</div>
<h2 class="font-bold tracking-tight lg:text-xl">
{data.student.first_name}
{data.student.last_name}
</h2>
<div class="flex flex-row gap-2">
<Badge variant="secondary" class="w-fit">
{data.student.driving_class}
</Badge>
</div>
</Card.Title>
<Card.Content class="mt-5 flex flex-col gap-4">
<div class="flex flex-row items-center gap-2">
<Mail size="18" />
<a class="text-sm text-muted-foreground" href="mailto:{data.student.email}">
{data.student.email}
</a>
</div>
<div class="flex flex-row items-center gap-2">
<Phone size="18" />
<span class="text-sm text-muted-foreground">
{data.student.phone_number}
</span>
</div>
</Card.Content>
</Card.Header>
</Card.Root>
</div>
<div class="space-y-4 xl:col-span-2">
<Card.Root>
<Card.Header class="relative">
<h2 class="mb-5">Schüler-Info</h2>
<Pencil
size="1.5rem"
class="text-muted-foreground hover:cursor-pointer hover:text-primary"
style="position: absolute; top: 0rem; right: 2rem;"
/>
<Card.Content class="px-0">
<form method="POST" use:enhance>
<input type="hidden" name="id" bind:value={$form.id} />
<div class="mb-4 flex flex-col gap-2">
<label for="first_name">Vorname</label>
<Input
type="text"
name="first_name"
class="input w-full"
bind:value={$form.first_name}
/>
{#if $errors.first_name}
<p class="text-error">{$errors.first_name}</p>
{/if}
</div>
<div class="mb-4 flex flex-col gap-2">
<label for="last_name">Nachname</label>
<Input
type="text"
name="last_name"
class="input"
bind:value={$form.last_name}
/>
{#if $errors.last_name}
<p class="text-error">{$errors.last_name}</p>
{/if}
</div>
<div class="mb-4 flex flex-col gap-2">
<label for="email">E-Mail Adresse</label>
<Input type="email" name="email" class="input" bind:value={$form.email} />
{#if $errors.email}
<p class="text-error">{$errors.email}</p>
{/if}
</div>
<div class="mb-4 flex flex-col gap-2">
<label for="phone_number">Telefonnummer</label>
<Input
type="tel"
name="phone_number"
class="input"
bind:value={$form.phone_number}
/>
{#if $errors.phone_number}
<p class="text-error">{$errors.phone_number}</p>
{/if}
</div>
<FormButton type="submit" class="w-full">Speichern</FormButton>
</form>
</Card.Content>
</Card.Header>
</Card.Root>
</div>
</div>
</Tabs.Content>
<Tabs.Content value="password">Change your password here.</Tabs.Content>
</Tabs.Root>
{:else}
<p>Fehler beim Laden des Schülers.</p>
{/if}
</div>
{/key}
{/if}