Search code examples
sveltekitzodsveltekit-superforms

Superforms, zod and server actions not running


I have an array of id's, i would like to display a button for each id which when clicked submits the id to an action in my page.server.ts. It should be simple but i'm pulling my hair out trying to get it to work.

Minimum reproducable example

example.schemas.ts

import { z } from "zod";
 
export const exampleSchema = z.object({
  exampleId: z.string().length(24),
});
 
export type ExampleSchema = typeof exampleSchema;

example-component.svelte

<script lang="ts">
    import { exampleSchema, type ExampleSchema } from "./example.schema";
    import {
        type SuperValidated,
        type Infer,
        superForm,
    } from "sveltekit-superforms";
    import SuperDebug from 'sveltekit-superforms';

    import { zodClient } from "sveltekit-superforms/adapters";

    export let data: SuperValidated<Infer<ExampleSchema>>;

    const form = superForm(data, {
        validators: zodClient(exampleSchema),
    });

    const { form: formData, enhance, message } = form;

    let exampleArray = ["507f1f77bcf86cd799439011","507f1f77bcf86cd799439012","507f1f77bcf86cd799439013"]

</script>

<form method="POST" action="?/exampleAction" use:enhance>
  {#each exampleArray as item}
    <input type="hidden" name="exampleId" value="{item}"/>
    <button type="submit" >{item}</button><br />
  {/each}
</form>
<SuperDebug data={formData} />

+page.server.ts

import type { PageServerLoad, Actions } from "./$types";
import { superValidate } from "sveltekit-superforms";
import { exampleSchema } from "$lib/components/custom/example.schema";
import { zod } from "sveltekit-superforms/adapters";

export const load: PageServerLoad = async () => {
  return {
      exampleForm: await superValidate(zod(exampleSchema)),
  };
};

export const actions: Actions = {
    exampleAction: async ({ request }) => {    
      const formData = await request.formData();
      const exampleId = formData.get('exampleId');
  
      // Process the ID as needed
      console.log('Submitted ID:', exampleId);
  
      return { success: true };
    }
};

+page.svelte

<script lang="ts">
  import type { PageData, ActionData } from './$types';
  import Example from "$lib/components/custom/example-component.svelte";
  
  export let data: PageData;

</script>

<Example data={data.exampleForm} />

SuperDebug always shows the exampleId as empty and validation always fails, it doesn't look like my action even runs at all.


Solution

  • The form is invalid because the exampleId is never transferred to the form data and thus the form is not submitted at all.

    Quite a few things about this are odd:

    • You have one form and multiple inputs with the same name but only expect one value to be sent.
      You probably should send the the value directly via the button instead of using an input.
      <button
          type="submit"
          name="exampleId" value={item}>
          {item}
      </button>
      
    • Validation is meant for user inputs, there is little point in validating a hidden field with a fixed value. You could omit validation on the client-side, then the form should be valid and submit.
      const form = superForm(data, {
          validators: zodClient(exampleSchema.omit({ exampleId: true })),
      });
      
      There is also an approach of setting the form data store property, which requires JS, e.g.
      <!-- only one input required -->
      <input type="hidden" name="exampleId" value={$formData.exampleId} />
      {#each exampleArray as item}
          <button
              type="submit"
              on:click={() => $formData.exampleId = item}>
              {item}
          </button>
          <br />
      {/each}
      
    • Validation on the server should be performed directly on the request and the validated form object should be returned according to docs.
      const form = await superValidate(request, zod(exampleSchema));
      
      console.log('Submitted ID:', form.data.exampleId);
      
      return { form };
      
    • (Form actions are bound to a page, putting the form in libs seems like it would likely lead to errors as the component is probably not compatible with most or any other pages.)