From 0607ffe0ea87fdae0fa293c4deae73810f056403 Mon Sep 17 00:00:00 2001 From: Davide Di Pumpo Date: Mon, 18 May 2026 15:11:31 +0200 Subject: [PATCH 1/2] fix: restore OmegaForm input reactivity with @tanstack/vue-form 1.32 @tanstack/vue-form inverted its Field slot reactivity between 1.23.5 and 1.32.0. The slot's `state` prop is now a one-time snapshot taken when the field mounts, while `field.state` is a reactive getter backed by the field store. OmegaForm bound inputs to the slot `state` prop, so the store updated but the view never did. Switch the Field slot consumers (OmegaInput, OmegaArray) to read from the reactive `field.state` instead, and correct the now-inverted doc comment in InputProps.ts. --- .../OmegaForm/InputReactivity.test.ts | 49 +++++++++++++++++++ .../src/components/OmegaForm/InputProps.ts | 2 +- .../src/components/OmegaForm/OmegaArray.vue | 11 +++-- .../src/components/OmegaForm/OmegaInput.vue | 10 +++- 4 files changed, 64 insertions(+), 8 deletions(-) create mode 100644 packages/vue-components/__tests__/OmegaForm/InputReactivity.test.ts diff --git a/packages/vue-components/__tests__/OmegaForm/InputReactivity.test.ts b/packages/vue-components/__tests__/OmegaForm/InputReactivity.test.ts new file mode 100644 index 0000000000..37b2b4996d --- /dev/null +++ b/packages/vue-components/__tests__/OmegaForm/InputReactivity.test.ts @@ -0,0 +1,49 @@ +import { mount } from "@vue/test-utils" +import * as S from "effect-app/Schema" +import { describe, expect, it } from "vitest" +import { useOmegaForm } from "../../src/components/OmegaForm" +import OmegaIntlProvider from "../OmegaIntlProvider.vue" + +describe("OmegaForm input reactivity", () => { + it("reflects value changes from the slot `state` in the view", async () => { + const schema = S.Struct({ + name: S.String + }) + + const wrapper = mount({ + components: { OmegaIntlProvider }, + template: ` + + + + + + + + `, + setup() { + const form = useOmegaForm(schema, { + defaultValues: { name: "" } + }) + return { form } + } + }) + + await wrapper.vm.$nextTick() + + const input = wrapper.find("[data-testid='input']") + await input.setValue("hello") + await wrapper.vm.$nextTick() + + // The slot `state` must stay in sync with the form store + expect(wrapper.find("[data-testid='display']").text()).toBe("hello") + expect((input.element as HTMLInputElement).value).toBe("hello") + }) +}) diff --git a/packages/vue-components/src/components/OmegaForm/InputProps.ts b/packages/vue-components/src/components/OmegaForm/InputProps.ts index 090c257ca0..eaec0cf14d 100644 --- a/packages/vue-components/src/components/OmegaForm/InputProps.ts +++ b/packages/vue-components/src/components/OmegaForm/InputProps.ts @@ -43,7 +43,7 @@ export type InputProps, TName extends Deep inputClass: string | undefined | null } field: OmegaFieldInternalApi - /** be sure to use this state and not `field.state` as it is not reactive */ + /** reactive field state, sourced from `field.state` (the Field slot's own `state` prop is a stale snapshot since @tanstack/vue-form 1.32) */ state: OmegaFieldInternalApi["state"] } diff --git a/packages/vue-components/src/components/OmegaForm/OmegaArray.vue b/packages/vue-components/src/components/OmegaForm/OmegaArray.vue index f6a630427a..ec1ca29ebf 100644 --- a/packages/vue-components/src/components/OmegaForm/OmegaArray.vue +++ b/packages/vue-components/src/components/OmegaForm/OmegaArray.vue @@ -3,10 +3,11 @@ :is="form.Field" :name="name" > -