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

Aperta Adatta ai principianti
#2,090 4 commenti 3 reazioni 0 assegnatari Vedi su GitHub

Nessuno ha ancora preso questa issue.

Valutazione

Difficoltà
2/5
Tempo stimato
1-3 ore
Idoneità per principianti
76/100
Tipo di issue
Bug
Chiarezza
Specificata chiaramente
Stato di attività
Attiva
Stack tecnologico
react, typescript
Ambito
frontend

Direzione di ricerca

Leggi LocalSubscribe in packages/react-form/src/useForm.tsx e ispeziona la chiamata a useStore e la relativa API della funzione di uguaglianza in @tanstack/react-store. Aggiorna la gestione dei selettori come descritto, incluso il selettore predefinito, quindi esegui i test esistenti e conferma che le selezioni di oggetti e array non vengano renderizzate nuovamente quando cambiano campi del modulo non correlati.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

v1 v2: needs investigation
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.

Lingua principale
TypeScript
Stelle
6.7k
Fork
682
Merge medio
5g 18h
PR unite (30g)
7

Guida per i contributori

Apri la guida per i contributori

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di TanStack/form

Tutte le issue di TanStack/form

Issue simili

Altre issue su TypeScript

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.