Skip to content

Commit dc4440f

Browse files
authored
fix(vue-form): keep field slot state reactive (#2192)
fix: keep vue field slot state reactive
1 parent 13f85fa commit dc4440f

3 files changed

Lines changed: 89 additions & 1 deletion

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@tanstack/vue-form': patch
3+
---
4+
5+
Fix `Field` scoped slot `state` reactivity after field value and meta updates.

packages/vue-form/src/useField.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -375,7 +375,12 @@ export function useField<
375375
},
376376
)
377377

378-
return { api: extendedFieldApi.value, state: fieldState.value } as const
378+
return {
379+
api: extendedFieldApi.value,
380+
get state() {
381+
return fieldState.value
382+
},
383+
} as const
379384
}
380385

381386
export type FieldComponentProps<

packages/vue-form/tests/useField.test.tsx

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,84 @@ describe('useField', () => {
225225
expect(getByText(error)).toBeInTheDocument()
226226
})
227227

228+
it('should keep field slot state reactive', async () => {
229+
type Person = {
230+
firstName: string
231+
}
232+
233+
const serverError = 'First name is already taken'
234+
235+
const Comp = defineComponent(() => {
236+
const form = useForm({
237+
defaultValues: {
238+
firstName: '',
239+
} as Person,
240+
})
241+
242+
function setServerError() {
243+
form.setFieldMeta('firstName', (meta) => ({
244+
...meta,
245+
errorMap: {
246+
...meta.errorMap,
247+
onServer: serverError,
248+
},
249+
errorSourceMap: {
250+
...meta.errorSourceMap,
251+
onServer: 'form',
252+
},
253+
}))
254+
}
255+
256+
return () => (
257+
<div>
258+
<button onClick={() => form.reset({ firstName: 'Ada' })}>
259+
Reset
260+
</button>
261+
<button onClick={setServerError}>Set server error</button>
262+
<form.Field name="firstName">
263+
{({
264+
field,
265+
state,
266+
}: {
267+
field: AnyFieldApi
268+
state: AnyFieldApi['state']
269+
}) => (
270+
<div>
271+
<input
272+
data-testid="fieldinput"
273+
value={state.value}
274+
onBlur={field.handleBlur}
275+
onInput={(e) =>
276+
field.handleChange((e.target as HTMLInputElement).value)
277+
}
278+
/>
279+
<p data-testid="slotvalue">{state.value}</p>
280+
<p data-testid="sloterror">{state.meta.errorMap.onServer}</p>
281+
</div>
282+
)}
283+
</form.Field>
284+
</div>
285+
)
286+
})
287+
288+
const { getByTestId, getByText } = render(Comp)
289+
const input = getByTestId('fieldinput')
290+
291+
await user.type(input, 'Grace')
292+
await waitFor(() =>
293+
expect(getByTestId('slotvalue')).toHaveTextContent('Grace'),
294+
)
295+
296+
await user.click(getByText('Reset'))
297+
await waitFor(() => expect(input).toHaveValue('Ada'))
298+
expect(getByTestId('slotvalue')).toHaveTextContent('Ada')
299+
300+
await user.click(getByText('Set server error'))
301+
await waitFor(() =>
302+
expect(getByTestId('sloterror')).toHaveTextContent(serverError),
303+
)
304+
})
305+
228306
it('should handle arrays with subvalues', async () => {
229307
const fn = vi.fn()
230308

0 commit comments

Comments
 (0)