Search code examples
typescriptformsvalidationsvelte

Binding number to input in Svelte and TypeScript


I have a form in svelte, using shadcn-svelte, and zod.

Relevant code snippets:

// schema.ts 

const formSchema = z.object({
  /* ... */
  maxParticipants: z.number()
});
<!-- form.svelte -->

<Form.Field {form} name="maxParticipants">
  <Form.Control let:attrs>
    <Form.Label>Max participants</Form.Label>
    <Input {...attrs} type="number" bind:value={$formData.maxParticipants} />
  </Form.Control>
  <Form.FieldErrors />
</Form.Field>

Because inputs are strings, even though the input is set to type="number", it's a string, which means that when I try to submit, I get an error underneath the input (in the FieldErrors, from the zod validation): "Expected number, received string".

I couldn't find a way to go about it, so all I have currently is just setting the input to type="text", and .string() in the zod schema, and then in the actions, where I handle the form submission, cast it to a number, but that's would be very not optimal.


Solution

  • For input elements of type="number" Svelte will convert the value to a number when binding, but the type has to be statically known, which is probably not the caes for this component (since the type is passed in here).

    You could create a more specific NumberInput which sets the type statically inside, then the value binding would yield number or null (if empty).

    Alternatively, do not use a binding and use an event to get the value out.
    Something like:

    <Input
      value={$formData.maxParticipants?.toString() ?? ''}
      on:change={e => $formData.maxParticipants = parseInt(e.target.value)} />
    

    (This is just for to illustrate the approach and does not handle everything properly, like the empty or white-space only cases.)