fix(react-form): remove no-deps layout effect in useField to prevent infinite loops#2019
Closed
yannisgu wants to merge 2 commits intoTanStack:mainfrom
Closed
fix(react-form): remove no-deps layout effect in useField to prevent infinite loops#2019yannisgu wants to merge 2 commits intoTanStack:mainfrom
yannisgu wants to merge 2 commits intoTanStack:mainfrom
Conversation
…infinite loops useField previously called fieldApi.update(opts) inside a layout effect with no dependency array, causing it to run during every commit phase. fieldApi.update() conditionally calls setFieldValue and setFieldMeta, which trigger store notifications and re-renders. Any component that triggers a state update or ref change during commit (e.g. Radix UI's composeRefs creating a new ref identity in React 19, or any component using useLayoutEffect with setState) would cause the layout effect to fire again, creating an infinite synchronous re-render loop that freezes the browser tab. The comment already noted that fieldApi.update "should not have any side effects" and should be "like a useRef". This change makes that literally true by directly assigning options and name during render, rather than calling the full update() method (which includes initialization side effects) in an effect. The initialization side effects (default value and meta registration) are already handled by fieldApi.mount(), which calls this.update() during the mount layout effect.
🦋 Changeset detectedLatest commit: 94f80f3 The changes in this PR will be included in the next version bump. This PR includes changesets to release 13 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Member
|
This fundamentally breaks how TanStack Form works. Do not open PRs to open-source projects with AI without reviewing manually, or else you will be banned from organizations for creating spam. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Note
This PR was generated by an AI agent (Claude Code). Human verification of the fix and its implications is still outstanding.
Summary
useIsomorphicLayoutEffect(() => { fieldApi.update(opts) })inuseFieldwith direct property assignment (fieldApi.options = opts; fieldApi.name = opts.name)Reproduction
Minimal repro repo: https://github.com/yannisgu/radix-tanstack-form-react19-repro
Problem
useFieldrunsfieldApi.update(opts)inside a layout effect with no dependency array — it fires during every commit phase. While the comment saysfieldApi.update"should not have any side effects", the implementation conditionally callssetFieldValueandsetFieldMeta, which trigger store notifications.This becomes an infinite loop when combined with any component that triggers a state update during commit:
fieldApi.update(opts)→setFieldMeta()→ store notification → re-renderThe loop is synchronous (happens during React's commit phase), so the browser tab freezes rather than throwing "Maximum update depth exceeded".
Affected libraries
This has been confirmed to cause infinite loops with:
@radix-ui/react-select—SelectTriggerpasses a state setter as a ref viacomposeRefs, which creates a new ref identity every render. In React 19, this causes ref unmount/remount during commit.@radix-ui/react-checkbox— UsesuseSize(element)which callssetSize()inuseLayoutEffect@dnd-kit/core—useDroppabledispatches actions inuseEffectFix
The comment already says this should be "like a
useRef". This PR makes that literally true by directly assigning options during render instead of calling the fullupdate()method in an effect.The initialization side effects in
fieldApi.update()(default value registration and meta initialization) are already handled byfieldApi.mount(), which callsthis.update(this.options)during the mount layout effect (which has[fieldApi]deps).Test plan
fieldApi.update()for initialization)form.Fieldwith Radix UI components no longer freezefieldApi.mount())