SvelteKit Remote Functions Workaround Helpers

Published: (December 7, 2025 at 06:48 PM EST)
2 min read
Source: Dev.to

Source: Dev.to

If you’re like me, you’ve been waiting for Remote Functions in SvelteKit for a long… long time… and they are awesome! Even though they are still experimental, the time they save is priceless. Below is a list of current limitations, workarounds, and their status.

1. Submit Type

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 = async ({ formElement }) => {
    // …
};

There isn’t a direct import for SubmitFunction, so you can derive it:

type SubmitRemoteFunction = Parameters[0];

const onSubmit: SubmitRemoteFunction = async ({ submit }) => {
    try {
        await submit();
    } catch (error) {
        console.error(error);
    }
};

Issue Tracker

2. Form Data

formData isn’t passed automatically, but you can derive it:

const onSubmit: SubmitRemoteFunction = async ({ submit, form }) => {
    const formData = new FormData(form);
    // optional conversion
    const data = Object.fromEntries(formData);
    // You shouldn’t need this since everything is available
    // in the form rune itself, but you never know…
};

Issue Tracker

3. Set Initial Data

The docs suggest using fields.set, but this isn’t dynamic and can cause a state_referenced_locally error. It also won’t automatically update values on subsequent changes. A fix is to create a helper that re‑initialises the form after hydration:

export function initForm(init: () => void) {
    init();

    let hydrated = false;

    $effect(() => {
        if (!hydrated) {
            hydrated = true;
            return;
        }
        init();
    });
}

Usage

initForm(() =>
    updateChapterAccessForm.fields.set({
        courseId: initialData.course_id,
        chapterId: initialData.id,
        isFree: !!initialData.is_free
    })
);

Issue Tracker

4. Form Debug

A small helper component to print the active form data during development (similar to SuperForms but using runes):

import type { RemoteFormFields, RemoteFormInput, RemoteForm } from '@sveltejs/kit';

const { form }: { form: RemoteForm } = $props();

const fields = $derived(form.fields as RemoteFormFields);

{JSON.stringify(
        {
            values: fields.value(),
            issues: fields.allIssues()
        },
        null,
        2
    )}

Usage

<!-- Insert component usage here -->

No issue tracker; this is just a development tool.

5. Touched

There’s currently no built‑in touched or pristine state. The following hook tracks whether the whole form has been modified:

import type { RemoteForm, RemoteFormInput } from "@sveltejs/kit";

export const useTouched = (
    form: RemoteForm
) => {
    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

Issue Tracker


That’s all for now. Feel free to share any additional workarounds you discover!

Back to Blog

Related posts

Read more »