Skip to content

Use Svelte 5 Runes for form data #577

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

Open
Tyler-Petrov opened this issue Mar 7, 2025 · 7 comments
Open

Use Svelte 5 Runes for form data #577

Tyler-Petrov opened this issue Mar 7, 2025 · 7 comments
Labels
enhancement New feature or request

Comments

@Tyler-Petrov
Copy link

First off I love the library, and it's helped my get a handle of my forms. With that in mind I have a suggestion.

Is your feature request related to a problem? Please describe.
Now that Svelte 5 Runes are a thing I'm not enjoying working with the SuperFormData store. I end up defining the super form, then setting a separate variable for the form data store itself so I can subscribe to it using bind:value on an input. As far as I can tell Svelte doesn't support subscribing to a property of an object. This really isn't a problem if there's only on form on a page, but let's be honest, that rarely happens. Most pages have 2 or more pages, and with a 5 to 10 pages like this things get crazy fast.

This would be a basic form that I would define

<script lang="ts">
	import SuperDebug, { superForm } from 'sveltekit-superforms';
	import type { PageData } from './$types';
	import { valibotClient } from 'sveltekit-superforms/adapters';
	import * as Form from '$lib/components/ui/form';
	import { Input } from '$shadcn/input';
	import { Button } from '$shadcn/button';
	import * as v from 'valibot';

	let { data }: { data: PageData } = $props();

	/* Defined somewhere the client and server can see it */
	const testVali = v.object({
		firstName: v.pipe(v.string()),
		lastName: v.pipe(v.string())
	});

	const testForm = superForm(data.testForm, {
		validators: valibotClient(testVali)
	});
	const testFormData = testForm.form;
</script>

<SuperDebug data={$testFormData} />

<form method="post" use:testForm.enhance>
	<input name="firstName" bind:value={$testFormData.firstName} />
	<input name="lastName" bind:value={$testFormData.lastName} />

	<button type="submit">Submit this here form</button>
</form>

Describe the solution you'd like
What I would like to see is a self contained class that can be built and extended with the form data as a Rune, so no $ subscribe is needed. Extending this class would be helpful for a form that is inside of a modal or sheet. The open property could be set of the new class, and keep everything self contained.

I'd like to see an implementation similar to this.

<script lang="ts">
	import SuperDebug from 'sveltekit-superforms';
	import type { PageData } from './$types';
	import { valibotClient } from 'sveltekit-superforms/adapters';
	import * as Form from '$lib/components/ui/form';
	import { Input } from '$shadcn/input';
	import { Button } from '$shadcn/button';
	import * as v from 'valibot';

	let { data }: { data: PageData } = $props();

	/* Defined somewhere the client and server can see it */
	const testVali = v.object({
		firstName: v.pipe(v.string()),
		lastName: v.pipe(v.string())
	});

	const testForm = SuperForm(data.testForm, {
		validators: valibotClient(testVali)
	});
</script>

<SuperDebug data={testForm.data} />

<form method="post" use:testForm.enhance>
	<input name="firstName" bind:value={testForm.data.firstName} />
	<input name="lastName" bind:value={testForm.data.lastName} />

	<button type="submit">Submit this here form</button>
</form>

Describe alternatives you've considered
I've tried building a class myself, but I keep running into the stores being an issue to subscribe to. I'm sure there's a somewhat simple way around it, but it seems like the idea I laid out would be helpful for others as well.

@Tyler-Petrov Tyler-Petrov added the enhancement New feature or request label Mar 7, 2025
@trancikk
Copy link

trancikk commented Mar 16, 2025

+1 for this, causing sveltejs/svelte#14306 when using superforms + multiselect https://github.com/janosh/svelte-multiselect on svelte 5.
WA is wrapping form store memebers into $state runes

@Tyler-Petrov
Copy link
Author

@trancikk I'm not 100% percent sure what you mean by WA. I'd be interested in your solution though. I've tried wrapping the store in a $state rune, but without much success.

@dualjack
Copy link

dualjack commented Apr 25, 2025

Hello! I will follow up with some kind of solution for now.
It seems SuperForms are not under heavy development recently.

Save this code snippet in some ts file:

import {type FormOptions, superForm, type SuperValidated} from "sveltekit-superforms";
import {fromStore} from "svelte/store";

export function superFormRunes<
    T extends Record<string, unknown> = Record<string, unknown>,
    M = any,
    In extends Record<string, unknown> = T
>(formValidated: SuperValidated<T, M, In> | T, formOptions?: FormOptions<T, M, In>){

    //  Use it locally.
    const formProxy = superForm<T, M, In>(formValidated, formOptions);

    //  Return unwrapped stores.
    return {
        ...formProxy,
        form: fromStore(formProxy.form),
        submitting: fromStore(formProxy.submitting),
        message: fromStore(formProxy.message),
        allErrors: fromStore(formProxy.allErrors),
        errors: fromStore(formProxy.errors),
        delayed: fromStore(formProxy.delayed),
        posted: fromStore(formProxy.posted),
        timeout: fromStore(formProxy.timeout),
    } satisfies {[key in keyof typeof formProxy]: any}

}

What id does:

  • wraps all store values in official helper objects (basically object with getter and setter)
  • to access value of wrapped store use X.current

Usage:

let {
    data
}: PageProps = $props();

const avatarForm = superFormRunes(data.formAvatar);

Why this is much better?

  • you do not have to expose values to use automatic store subscription ( ex. $form, $message )
<form enctype="multipart/form-data" method="POST" action="?/avatar" use:avatarForm.enhance>
    <input type="text" bind:value={avatarForm.form.current.text}>
</form>

@ciscoheat
Copy link
Owner

That should work in many cases, a problem arises though when you want to add options to certain updates, like this one:

form.update(
  ($form) => {
    $form.name = "New name";
    return $form;
  },
  { taint: false }
)

@Tyler-Petrov
Copy link
Author

@dualjack I didn't know Svelte had that fromStore utility function. I'll look into that further.

@ciscoheat That's a good point. I'll mess around with some possible solutions or my project, and if I stumble across anything good, then I'll post it here.

Thanks guys!

@dualjack
Copy link

@Tyler-Petrov Me neither. It is not documented at all. Just a reference in API.
https://svelte.dev/docs/svelte/svelte-store#fromStore

Image

@rmunn
Copy link

rmunn commented May 14, 2025

@ciscoheat That's a good point. I'll mess around with some possible solutions or my project, and if I stumble across anything good, then I'll post it here.

My first thought was to expose the original, unwrapped objects with names like formStore, then you can use the form state variable for most things but drop down to formStore when you need to do something like form.update(..., { taint: false }) (which would simply become formStore.update(..., { taint: false })).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

5 participants