If you're like me, you've been waiting for Remote Functions in SvelteKit for a long... long time... and they are awesome!
It will be extremely hard to ever go back. Even though they are still experimental, the time they save is priceless.
However, not every feature is ready. So, I am posting a list of them, and their workarounds, and their status.
1. Submit Type
This is the least important, but needed. You may want to declare your enhance function outside the component template.
In form actions, you use types like this:
import type { SubmitFunction } from '@sveltejs/kit';
const onSubmit: SubmitFunction<MyFormType> = async ({ formElement }) => {
...
<form method="POST" action="/" use:enhance={onSubmit}>
There isn't a direct import for SubmitFunction, so you can derive it.
type SubmitRemoteFunction = Parameters<typeof myForm.enhance>[0];
const onSubmit: SubmitRemoteFunction = async ({ submit }) => {
try {
await submit();
} catch (error) {
console.error(error);
}
};
<form {...myForm.preflight(myFormSchema).enhance(onSubmit)}>
2. Form Data
Again, super small, but there is no formData passed. However, you can easily derive it.
const onSubmit: SubmitRemoteFunction = async ({ submit, form }) => {
const formData = new FormData(form);
// optional
const data = Object.fromEntries(formData);
// You shouldn't need this since everything is available
// in the form rune itself, but you never know...
3. Set Initial Data
Currently, the docs say to set the initial data by using fields.set. However, this is not dynamic, and will cause a state_referenced_locally error.
It also won't automatically set the values on updates. Here is a fix.
import type { RemoteForm, RemoteFormInput } from "@sveltejs/kit";
export function initForm<T extends RemoteFormInput, R = unknown>(
form: RemoteForm<T, R>,
getter: () => Parameters<typeof form.fields.set>[0]
) {
let hydrated = false;
form.fields.set(getter());
$effect(() => {
const values = getter();
if (!hydrated) {
hydrated = true;
return;
}
form.fields.set(values);
});
};
This will properly hydrate the form, while keeping it reactive. It will NOT run extraneously on hydration, but will be reactive.
Usage
initForm(updateChapterForm, () => ({
courseId: initialData.course_id,
chapterId: initialData.id,
title: initialData.title
}));
Issue Tracker for this to be fixed.
4. Form Debug
This is not an issue, but a helper. We need a component to print the active form data for development similar to SuperForms (but actually uses runes instead of stores):
<script lang="ts" generics="T extends RemoteFormInput">
import type { RemoteFormFields, RemoteFormInput } from '@sveltejs/kit';
import type { RemoteForm } from '@sveltejs/kit';
const { form }: { form: RemoteForm<T, void> } = $props();
const fields = $derived(form.fields as RemoteFormFields<RemoteFormInput>);
</script>
<pre
class="mt-4 max-h-96 overflow-auto rounded-lg bg-gray-900 p-4 text-sm wrap-break-word whitespace-pre-wrap text-green-300">
{JSON.stringify(
{
values: fields.value(),
issues: fields.allIssues()
},
null,
2
)}
</pre>
You can use it like so:
<FormDebug form={updateChapterForm} />
No Issue Tracker, as it is just a tool.
4. Touched
While there is no touched or pristine yet, I made a hook. However, this only works on the entire form, not individual fields. If anyone wants to build a hook, let me know, but hopefully this gets implemented sooner than later internally.
import type { RemoteForm, RemoteFormInput } from "@sveltejs/kit";
export const useTouched = <T extends void | RemoteFormInput, R = unknown>
(form: RemoteForm<T, R>) => {
const initialValue = JSON.stringify(form.fields.value());
return {
get touched() {
return JSON.stringify(form.fields.value()) !== initialValue;
}
}
};
Usage:
const touchForm = useTouched(myForm);
const isSubmitting = $derived(!!myForm.pending);
const isValid = $derived(!myForm.fields.allIssues() && touchForm.touched);
// touchForm.touched
And that is all I have for now, just wanted to put all these in one place.
Let me know your work arounds for now!
J


Top comments (0)