svelte-forms-lib
svelte-forms-lib copied to clipboard
Form array doesn't work with Typescript
Summary
Steps to reproduce
Copy paste the form array code example here and change the script lang for ts
Example Project
<script lang="ts">
import { createForm } from 'svelte-forms-lib';
import * as yup from 'yup';
const { form, errors, state, handleChange, handleSubmit, handleReset } = createForm({
initialValues: {
users: [
{
name: '',
email: '',
},
],
},
validationSchema: yup.object().shape({
users: yup.array().of(
yup.object().shape({
name: yup.string().required(),
email: yup.string().email().required(),
})
),
}),
onSubmit: (values) => {
alert(JSON.stringify(values));
},
});
const add = () => {
$form.users = $form.users.concat({ name: '', email: '' });
$errors.users = $errors.users.concat({ name: '', email: '' });
};
const remove = (i) => () => {
$form.users = $form.users.filter((u, j) => j !== i);
$errors.users = $errors.users.filter((u, j) => j !== i);
};
</script>
<form>
<h1>Add users</h1>
{#each $form.users as user, j}
<div class="form-group">
<div>
<input
name={`users[${j}].name`}
placeholder="name"
on:change={handleChange}
on:blur={handleChange}
bind:value={$form.users[j].name}
/>
{#if $errors.users[j].name}
<small class="error">{$errors.users[j].name}</small>
{/if}
</div>
<div>
<input
placeholder="email"
name={`users[${j}].email`}
on:change={handleChange}
on:blur={handleChange}
bind:value={$form.users[j].email}
/>
{#if $errors.users[j].email}
<small class="error">{$errors.users[j].email}</small>
{/if}
</div>
{#if j === $form.users.length - 1}
<button type="button" on:click={add}>+</button>
{/if}
{#if $form.users.length !== 1}
<button type="button" on:click={remove(j)}>-</button>
{/if}
</div>
{/each}
<div class="button-group">
<button type="button" on:click={handleSubmit}>submit</button>
<button type="button" on:click={handleReset}>reset</button>
</div>
</form>
<style>
.error {
display: block;
color: red;
}
.form-group {
display: flex;
align-items: baseline;
}
.button-group {
display: flex;
}
button ~ button {
margin-left: 15px;
}
</style>
What is the current bug behavior?
There are some typescript errors :
- Argument of type '{ name: string; email: string; }' is not assignable to parameter of type 'string'.
- Property 'name' does not exist on type 'string'
What is the expected correct behavior?
No typescript error anymore
Relevant logs and/or screenshots
Same experience on using a yup.array(yup.string())
.
Currently my workaround is to explicitly cast:
$: fruitErrors = $errors.fruits as unknown as string[];
Looks like this comes from $errors
as being a record whose values are always string
:
https://github.com/tjinauyeung/svelte-forms-lib/blob/master/lib/index.d.ts#L34
Should errors
be Writable<Inf>
directly, so its values are supposed to have the same shape as the form values? Looks like this is what svelte-forms-lib actually exposes. At least when using yup
-- this seems to be specific:
https://github.com/tjinauyeung/svelte-forms-lib/blob/7df1b1df4f02c5ff512e2542c879056ac2d8138d/lib/create-form.js#L142-L164
I'm not TypeScript definitions guru, but I assume it would be possible to refine createForm
and FormState
declarations to accept a generic parameter TShape extends yup.ObjectShape
which would be plugged into validationSchema: ObjectSchema<TShape>
and into errors: Writable<TShape>
. Currently validationSchema
is marked as optional but it seems to be the condition between a generic form and a yup-powered form.
Another solution to this is to cast is before assigning it to <Form>.
import type { FormProps } from 'svelte-forms-lib';
import { Form, Field } from 'svelte-forms-lib';
// Specify your own form field type, to you have safe typing
type FormFields = {
email: string
username: string
password: string
}
const formProps: FormProps<FormFields> = {
initialValues: {}
.... etc
}
// Cast formProps to the type <Form> expects
const props = formProps as FormProps; // ← ← ←
Then assign props
instead of formProps
.
<Form {...props}>
<!-- form contents -->
</Form>
But this behavior does seem like a bug.