Skip to content

fix(vue-form): losing reactivity #1371

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 19 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 14 additions & 18 deletions docs/framework/vue/guides/arrays.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,24 @@ with [`Index` from `solid-js`](https://www.solidjs.com/tutorial/flow_index):
<script setup lang="ts">
import { useForm } from '@tanstack/vue-form'

const form = useForm({
const form = useForm(() => ({
defaultValues: {
people: [] as Array<{ age: number; name: string }>,
},
onSubmit: ({ value }) => alert(JSON.stringify(value)),
})
}))
</script>

<template>
<form.Field name="people">
<template v-slot="{ field, state }">
<template v-slot="{ field, value }">
<div>
<form.Field
v-for="(_, i) of field.state.value"
v-for="(_, i) of value()"
:key="i"
:name="`people[${i}].name`"
>
<template v-slot="{ field: subField, state }">
<template v-slot="{ field: subField, meta }">
<!-- ... -->
</template>
</form.Field>
Expand All @@ -52,17 +52,13 @@ This will generate the mapped slot every time you run `pushValue` on `field`:
Finally, you can use a subfield like so:

```vue
<form.Field
v-for="(_, i) of field.state.value"
:key="i"
:name="`people[${i}].name`"
>
<template v-slot="{ field: subField, state }">
<form.Field v-for="(_, i) of value()" :key="i" :name="`people[${i}].name`">
<template v-slot="{ field: subField, value }">
<div>
<label>
<div>Name for person {{ i }}</div>
<input
:value="subField.state.value"
:value="value()"
@input="
(e) =>
subField.handleChange(
Expand All @@ -82,12 +78,12 @@ Finally, you can use a subfield like so:
<script setup lang="ts">
import { useForm } from '@tanstack/vue-form'

const form = useForm({
const form = useForm(() => ({
defaultValues: {
people: [] as Array<{ age: number; name: string }>,
},
onSubmit: ({ value }) => alert(JSON.stringify(value)),
})
}))
</script>

<template>
Expand All @@ -102,19 +98,19 @@ const form = useForm({
>
<div>
<form.Field name="people">
<template v-slot="{ field, state }">
<template v-slot="{ value }">
<div>
<form.Field
v-for="(_, i) of field.state.value"
v-for="(_, i) of value()"
:key="i"
:name="`people[${i}].name`"
>
<template v-slot="{ field: subField, state }">
<template v-slot="{ field: subField, value }">
<div>
<label>
<div>Name for person {{ i }}</div>
<input
:value="subField.state.value"
:value="value()"
@input="
(e) =>
subField.handleChange(
Expand Down
17 changes: 6 additions & 11 deletions docs/framework/vue/guides/async-initial-values.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,21 +31,16 @@ const { data, isLoading } = useQuery({
},
})

const firstName = computed(() => data.value?.firstName || '')
const lastName = computed(() => data.value?.lastName || '')

const defaultValues = reactive({
firstName,
lastName,
})

const form = useForm({
defaultValues,
const form = useForm(() => ({
defaultValues: {
firstName: data.value?.firstName || '',
lastName: data.value?.lastName || '',
},
onSubmit: async ({ value }) => {
// Do something with form data
console.log(value)
},
})
}))
</script>

<template>
Expand Down
48 changes: 24 additions & 24 deletions docs/framework/vue/guides/basic-concepts.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,19 @@ const formOpts = formOptions({
A Form Instance is an object that represents an individual form and provides methods and properties for working with the form. You create a form instance using the `useForm` function. The function accepts an object with an `onSubmit` function, which is called when the form is submitted.

```js
const form = useForm({
const form = useForm(() => ({
...formOpts,
onSubmit: async ({ value }) => {
// Do something with form data
console.log(value)
},
})
}))
```

You may also create a form instance without using `formOptions` by using the standalone `useForm` API:

```ts
const form = useForm({
const form = useForm(() => ({
onSubmit: async ({ value }) => {
// Do something with form data
console.log(value)
Expand All @@ -48,7 +48,7 @@ const form = useForm({
lastName: '',
hobbies: [],
} as Person,
})
}))
```

## Field
Expand All @@ -61,10 +61,10 @@ Example:
<template>
<!-- ... -->
<form.Field name="fullName">
<template v-slot="{ field }">
<template v-slot="{ field, value }">
<input
:name="field.name"
:value="field.state.value"
:value="value()"
@blur="field.handleBlur"
@input="(e) => field.handleChange(e.target.value)"
/>
Expand Down Expand Up @@ -102,10 +102,10 @@ The Field API is an object provided by a scoped slot using the `v-slot` directiv
Example:

```vue
<template v-slot="{ field }">
<template v-slot="{ field, value }">
<input
:name="field.name"
:value="field.state.value"
:value="value()"
@blur="field.handleBlur"
@input="(e) => field.handleChange(e.target.value)"
/>
Expand Down Expand Up @@ -136,13 +136,13 @@ Example:
},
}"
>
<template v-slot="{ field }">
<template v-slot="{ field, value, meta }">
<input
:value="field.state.value"
:value="value()"
@input="(e) => field.handleChange(e.target.value)"
@blur="field.handleBlur"
/>
<FieldInfo :field="field" />
<FieldInfo :meta="meta()" />
</template>
</form.Field>
<!-- ... -->
Expand All @@ -166,9 +166,9 @@ Supported libraries include:
import { useForm } from '@tanstack/vue-form'
import { z } from 'zod'

const form = useForm({
const form = useForm(() => ({
// ...
})
}))

const onChangeFirstName = z.string().refine(
async (value) => {
Expand All @@ -191,16 +191,16 @@ const onChangeFirstName = z.string().refine(
onChangeAsync: onChangeFirstName,
}"
>
<template v-slot="{ field, state }">
<template v-slot="{ field, value, meta }">
<label :htmlFor="field.name">First Name:</label>
<input
:id="field.name"
:name="field.name"
:value="field.state.value"
:value="value()"
@input="(e) => field.handleChange((e.target as HTMLInputElement).value)"
@blur="field.handleBlur"
/>
<FieldInfo :state="state" />
<FieldInfo :meta="meta()" />
</template>
</form.Field>
<!-- ... -->
Expand Down Expand Up @@ -249,9 +249,9 @@ Example:
},
}"
>
<template v-slot="{ field }">
<template v-slot="{ field, value }">
<input
:value="field.state.value"
:value="value()"
@input="(e) => field.handleChange(e.target.value)"
/>
</template>
Expand Down Expand Up @@ -290,13 +290,13 @@ Example:
<div v-else>
<div v-for="(_, i) in hobbiesField.state.value" :key="i">
<form.Field :name="`hobbies[${i}].name`">
<template v-slot="{ field }">
<template v-slot="{ field, value, meta }">
<div>
<label :for="field.name">Name:</label>
<input
:id="field.name"
:name="field.name"
:value="field.state.value"
:value="value()"
@blur="field.handleBlur"
@input="(e) => field.handleChange(e.target.value)"
/>
Expand All @@ -306,22 +306,22 @@ Example:
>
X
</button>
<FieldInfo :field="field" />
<FieldInfo :meta="meta()" />
</div>
</template>
</form.Field>
<form.Field :name="`hobbies[${i}].description`">
<template v-slot="{ field }">
<template v-slot="{ field, value, meta }">
<div>
<label :for="field.name">Description:</label>
<input
:id="field.name"
:name="field.name"
:value="field.state.value"
:value="value()"
@blur="field.handleBlur"
@input="(e) => field.handleChange(e.target.value)"
/>
<FieldInfo :field="field" />
<FieldInfo :meta="meta()" />
</div>
</template>
</form.Field>
Expand Down
14 changes: 7 additions & 7 deletions docs/framework/vue/guides/linked-fields.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,24 +23,24 @@ To do this, you can add a `onChangeListenTo` property to the `confirm_password`
<script setup lang="ts">
import { useForm } from '@tanstack/vue-form'

const form = useForm({
const form = useForm(() => ({
defaultValues: {
password: '',
confirm_password: '',
},
// ...
})
}))
</script>

<template>
<div>
<form @submit.prevent.stop="form.handleSubmit">
<div>
<form.Field name="password">
<template v-slot="{ field }">
<template v-slot="{ field, value }">
<div>Password:</div>
<input
:value="field.state.value"
:value="value()"
@input="
(e) => field.handleChange((e.target as HTMLInputElement).value)
"
Expand All @@ -59,15 +59,15 @@ const form = useForm({
},
}"
>
<template v-slot="{ field }">
<template v-slot="{ field, value, meta }">
<div>Confirm Password:</div>
<input
:value="field.state.value"
:value="value()"
@input="
(e) => field.handleChange((e.target as HTMLInputElement).value)
"
/>
<div v-for="(err, index) in field.state.meta.errors" :key="index">
<div v-for="(err, index) in meta().errors" :key="index">
{{ err }}
</div>
</template>
Expand Down
12 changes: 6 additions & 6 deletions docs/framework/vue/guides/listeners.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,13 @@ Events that can be "listened" to are:
<script setup>
import { useForm } from '@tanstack/vue-form'

const form = useForm({
const form = useForm(() => ({
defaultValues: {
country: '',
province: '',
},
// ...
})
}))
</script>

<template>
Expand All @@ -44,18 +44,18 @@ const form = useForm({
},
}"
>
<template v-slot="{ field }">
<template v-slot="{ field, value }">
<input
:value="field.state.value"
:value="value()"
@input="(e) => field.handleChange(e.target.value)"
/>
</template>
</form.Field>

<form.Field name="province">
<template v-slot="{ field }">
<template v-slot="{ field, value }">
<input
:value="field.state.value"
:value="value()"
@input="(e) => field.handleChange(e.target.value)"
/>
</template>
Expand Down
Loading