ciscoheat / sveltekit-superforms

Making SvelteKit forms a pleasure to use!
https://superforms.rocks
MIT License
2.11k stars 62 forks source link

Snapshots with componentized form #412

Open singingwolfboy opened 4 months ago

singingwolfboy commented 4 months ago

I'm trying to factor out my form into a component, as described in the documentation. However, I can't figure out how to export a snapshot of my form when doing so. The documentation clearly states that a snapshot object must be exported from a +page.svelte or +layout.svelte file; exporting it from a component file won't work.

So if my form component looks like this:

<!-- MyForm.svelte -->
<script lang="ts">
  import { superForm, type SuperValidated, type Infer } from 'sveltekit-superforms/client';
  import { schema } from './schema';

  export let data: SuperValidated<Infer<typeof schema>>;

  const form = superForm(data, {
    SPA: true,
    validators: zod(schema),
    ...
  });
  const { form: formData, capture, restore, enhance } = form;
  // `capture` and `restore` are available here, inside the component
</script>

<form use:enhance method="POST">
  <!-- form fields here -->
</form>

And my page looks like this:

<!-- +page.svelte -->
<script lang="ts">
  import MyForm from './MyForm.svelte'

  export let data;
  // need to get the `capture` and `restore` functions here, so I can export them from the page!
</script>

<MyForm data={data.form} />

How do I make this work?

Note that this seems to be fundamentally the same question as #162, but I don't understand the resolution to that issue.

ciscoheat commented 4 months ago

You need to export a custom capture and restore object, similar to this, and then bind it to the component, so the component can modify it.

singingwolfboy commented 4 months ago

I ended up doing this:

<!-- MyForm.svelte -->
<script lang="ts">
  import type { Snapshot } from '@sveltejs/kit';
  import { superForm, type SuperValidated, type Infer } from 'sveltekit-superforms/client';
  import { schema } from './schema';

  export let data: SuperValidated<Infer<typeof schema>>;
  export let snapshot: Snapshot;

  const form = superForm(data, {
    SPA: true,
    validators: zod(schema),
    ...
  });
  const { form: formData, capture, restore, enhance } = form;
  snapshot = { capture, restore }
</script>

<form use:enhance method="POST">
  <!-- form fields here -->
</form>
<!-- +page.svelte -->
<script lang="ts">
  import type { Snapshot } from '@sveltejs/kit';
  import MyForm from './MyForm.svelte'

  export let data;
  export let snapshot: Snapshot = { capture() {}, restore(snapshot) {} };
</script>

<!-- not sure why this is needed, but it doesn't seem to work without it... -->
<svelte:options accessors />

<MyForm data={data.form} bind:snapshot />

Is that basically what you had in mind? I'm not sure if I need to be concerned about the accessors thing, or why it's needed. I'm also a bit surprised that this works; I thought that the restore function would run when the page component loaded, before the child <MyForm> component had a chance to set it to something meaningful. It seems to work, but I don't know if this introduces a race condition or not.