diff --git a/packages/preact-form/src/useFormGroup.tsx b/packages/preact-form/src/useFormGroup.tsx index 3cd22d6a0d..e5e6f438bd 100644 --- a/packages/preact-form/src/useFormGroup.tsx +++ b/packages/preact-form/src/useFormGroup.tsx @@ -1,6 +1,6 @@ import { useMemo, useState } from 'preact/hooks' import { useSelector } from '@tanstack/preact-store' -import { FormGroupApi, functionalUpdate } from '@tanstack/form-core' +import { FormGroupApi, functionalUpdate, getBy } from '@tanstack/form-core' import { useIsomorphicLayoutEffect } from './useIsomorphicLayoutEffect' import type { DeepKeys, @@ -201,9 +201,12 @@ export function useFormGroup< setPrevOptions({ form: opts.form, name: opts.name }) } + // Subscribe to the form's baseStore with a path-based selector so this hook + // only re-renders when a field inside *this* group changes, not when any + // sibling group's value changes (fixes issue #2377). const reactiveStateValue = useSelector( - formGroupApi.store, - (state) => state.value, + formGroupApi.form.baseStore, + (state) => getBy(state.values, opts.name), ) const reactiveMetaIsTouched = useSelector( diff --git a/packages/react-form/src/useFormGroup.tsx b/packages/react-form/src/useFormGroup.tsx index d9d02f4989..5ff9a4c156 100644 --- a/packages/react-form/src/useFormGroup.tsx +++ b/packages/react-form/src/useFormGroup.tsx @@ -2,7 +2,7 @@ import { useMemo, useState } from 'react' import { useSelector } from '@tanstack/react-store' -import { FormGroupApi, functionalUpdate } from '@tanstack/form-core' +import { FormGroupApi, functionalUpdate, getBy } from '@tanstack/form-core' import { useIsomorphicLayoutEffect } from './useIsomorphicLayoutEffect' import type { DeepKeys, @@ -203,9 +203,12 @@ export function useFormGroup< setPrevOptions({ form: opts.form, name: opts.name }) } + // Subscribe to the form's baseStore with a path-based selector so this hook + // only re-renders when a field inside *this* group changes, not when any + // sibling group's value changes (fixes issue #2377). const reactiveStateValue = useSelector( - formGroupApi.store, - (state) => state.value, + formGroupApi.form.baseStore, + (state) => getBy(state.values, opts.name), ) const reactiveMetaIsTouched = useSelector( diff --git a/packages/react-form/tests/useFormGroup.test.tsx b/packages/react-form/tests/useFormGroup.test.tsx index d8eabf34f3..4dbb9cb9b5 100644 --- a/packages/react-form/tests/useFormGroup.test.tsx +++ b/packages/react-form/tests/useFormGroup.test.tsx @@ -320,4 +320,70 @@ describe('form.FormGroup', () => { expect(button.disabled).toBe(false) expect(onGroupSubmit).toHaveBeenCalledTimes(1) }) + + // Regression test for https://github.com/TanStack/form/issues/2377 + it('should not re-render sibling field components when a field value changes', async () => { + const step1NameRenderCount = { current: 0 } + + function Comp() { + const form = useForm({ + defaultValues: { + step1: { name: '' }, + step2: { name: '' }, + }, + }) + + return ( +