diff --git a/.changeset/puny-wombats-tap.md b/.changeset/puny-wombats-tap.md new file mode 100644 index 000000000..3b7362a53 --- /dev/null +++ b/.changeset/puny-wombats-tap.md @@ -0,0 +1,6 @@ +--- +'@tanstack/solid-form': patch +'@tanstack/form-core': patch +--- + +Fix double-rendering of Solid fields diff --git a/packages/form-core/src/FieldApi.ts b/packages/form-core/src/FieldApi.ts index 55293ce2e..1d5ba3096 100644 --- a/packages/form-core/src/FieldApi.ts +++ b/packages/form-core/src/FieldApi.ts @@ -1169,7 +1169,33 @@ export class FieldApi< this.store = new Derived({ deps: [this.form.store], - fn: () => { + fn: ({ prevVal: _prevVal }) => { + const prevVal = _prevVal as + | FieldState< + TParentData, + TName, + TData, + TOnMount, + TOnChange, + TOnChangeAsync, + TOnBlur, + TOnBlurAsync, + TOnSubmit, + TOnSubmitAsync, + TOnDynamic, + TOnDynamicAsync, + TFormOnMount, + TFormOnChange, + TFormOnChangeAsync, + TFormOnBlur, + TFormOnBlurAsync, + TFormOnSubmit, + TFormOnSubmitAsync, + TFormOnDynamic, + TFormOnDynamicAsync + > + | undefined + const meta = this.form.getFieldMeta(this.name) ?? { ...defaultFieldMeta, ...opts.defaultMeta, @@ -1185,6 +1211,10 @@ export class FieldApi< value = this.options.defaultValue } + if (prevVal && prevVal.value === value && prevVal.meta === meta) { + return prevVal + } + return { value, meta, diff --git a/packages/solid-form/src/createField.tsx b/packages/solid-form/src/createField.tsx index 6f689fc37..504564b03 100644 --- a/packages/solid-form/src/createField.tsx +++ b/packages/solid-form/src/createField.tsx @@ -6,6 +6,7 @@ import { onCleanup, onMount, } from 'solid-js' +import { useStore } from '@tanstack/solid-store' import type { DeepKeys, DeepValue, @@ -251,8 +252,14 @@ function makeFieldReactive< TParentSubmitMeta > { const [field, setField] = createSignal(fieldApi, { equals: false }) - const unsubscribeStore = fieldApi.store.subscribe(() => setField(fieldApi)) - onCleanup(unsubscribeStore) + // Handle shallow comparison to make sure that Derived doesn't create a new setField call every time + const store = useStore(fieldApi.store, (store) => store) + // Run before initial render + createComputed(() => { + // Use the store to track dependencies + store() + setField(fieldApi) + }) return field }