TanStack / TanStack/form

perf(react-form): `form.Subscribe` causes unnecessary re-renders when selector returns objects or arrays with same content

Open Beginner friendly
#2,090 4 comments 3 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

v1 v2: needs investigation
Dominant language
TypeScript
Stars
6.7k
Forks
682
Avg merge
5d 18h
Merged PRs (30d)
7

Description

Describe the bug

form.Subscribe triggers unnecessary re-renders when the selector returns an object or array, even when the underlying values have not changed. This is because LocalSubscribe in packages/react-form/src/useForm.tsx calls useStore(form.store, selector) without an equality function, so it falls back to Object.is reference equality. Since selectors that return objects/arrays create a new reference on every store update, the component re-renders every time any field in the form changes — not just the fields the selector cares about.

Screen recording

Image

Your minimal, reproducible example

https://codesandbox.io/p/github/kevalmiistry/form-test/main?import=true

Steps to reproduce

CodeSandBox

  1. Open React DevTools and enable "Highlight updates when components render"
  2. Set Account Type to "Business"
  3. Set Country to "US"
  4. The Tax ID field appears (rendered inside the form.Subscribe block)
  5. Now type in any other input (e.g. the Name field)
  6. Observe that the form.Subscribe block (and the Tax ID field inside it) flashes/re-renders on every keystroke, even though accountType and country have not changed
Expected behavior

form.Subscribe should only re-render when the content of the selected value actually changes, not when the reference changes. A shallow equality check would solve this for the common patterns above (objects and arrays with primitive values).

How often does this bug happen?

Every time

Screenshots or Videos

No response

Platform
  • OS: macOS Sequoia (Darwin 25.3.0)
  • Browser: Chrome (latest)
  • @tanstack/react-form — react adapter
  • Tested on latest main branch (commit e21cc011)
TanStack Form adapter

react-form

TanStack Form version

1.28.5

TypeScript version

No response

Additional context
Root cause

In packages/react-form/src/useForm.tsx, the LocalSubscribe component:

function LocalSubscribe({ form, selector = (state) => state, children }) {
  const data = useStore(form.store, selector)  // ← no equality function
  return <>{functionalUpdate(children, data)}</>
}

useStore from @tanstack/react-store accepts an optional third argument — an equality function. Without it, Object.is is used, which fails for objects and arrays.

Proposed fix
  1. Import shallow from @tanstack/react-store (already a dependency, zero cost)
  2. Pass shallow as the third argument to useStore
  3. Extract the default selector to a module-level constant to fix the @eslint-react/no-unstable-default-props lint warning
import { shallow, useStore } from '@tanstack/react-store'

const defaultSelector = (state: AnyFormState) => state

function LocalSubscribe({
  form,
  selector = defaultSelector,
  children,
}: PropsWithChildren<{
  form: AnyFormApi
  selector?: (state: AnyFormState) => AnyFormState
}>) {
  const data = useStore(form.store, selector, shallow)
  return <>{functionalUpdate(children, data)}</>
}

This is a non-breaking change. shallow correctly handles primitives (falls back to Object.is), plain objects, arrays, Maps, Sets, and Dates. All existing tests pass.

I have already implemented this in my local setup and can confirm it is working as expected and all tests are passing post changes. I can open a PR for this if the approach looks good. In that case please assign the issue to me.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Read LocalSubscribe in packages/react-form/src/useForm.tsx and inspect the useStore call and its equality-function API in @tanstack/react-store. Update the selector handling as described, including the default selector, then run the existing tests and confirm that object and array selections do not re-render when unrelated form fields change.

Written by the indexing model from the issue text.

Assessment

Tech stack
react, typescript
Domain
frontend
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
76/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.