Skip to content

fix(vue-form): prevent full array re-renders in array mode#1963

Merged
crutchcorn merged 10 commits into
TanStack:mainfrom
imramkrishna:fix/vue-array-field-rerender
May 10, 2026
Merged

fix(vue-form): prevent full array re-renders in array mode#1963
crutchcorn merged 10 commits into
TanStack:mainfrom
imramkrishna:fix/vue-array-field-rerender

Conversation

@imramkrishna
Copy link
Copy Markdown
Contributor

@imramkrishna imramkrishna commented Dec 31, 2025

Closes #1961

Problem

In the Vue adapter, useField currently subscribes to the entire field state via:

useStore(fieldApi.store, (state) => state)

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

* **Improvements**
  * Reduced unnecessary re-renders by refining how field state is tracked; array-mode fields now track length for efficient updates.
  * Field meta (touched, blurred, dirty, errors, validating) updates independently for more accurate UI feedback.

* **Tests**
  * Added a test verifying array-mode fields update and render correctly when pushed.

* **Documentation**
  * Updated Vue guides to explicitly show mode="array" for array fields.

* **Examples**
  * Example app updated to use array-mode field configuration.

[![Review Change Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/TanStack/form/pull/1963)
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Dec 31, 2025

⚠️ No Changeset found

Latest commit: fd2732e

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@nx-cloud
Copy link
Copy Markdown

nx-cloud Bot commented Dec 31, 2025

View your CI Pipeline Execution ↗ for commit 666343b

Command Status Duration Result
nx affected --targets=test:sherif,test:knip,tes... ✅ Succeeded 43s View ↗
nx run-many --target=build --exclude=examples/** ✅ Succeeded 6s View ↗

☁️ Nx Cloud last updated this comment at 2026-05-10 20:45:00 UTC

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Dec 31, 2025

More templates

@tanstack/angular-form

npm i https://pkg.pr.new/@tanstack/angular-form@1963

@tanstack/form-core

npm i https://pkg.pr.new/@tanstack/form-core@1963

@tanstack/form-devtools

npm i https://pkg.pr.new/@tanstack/form-devtools@1963

@tanstack/lit-form

npm i https://pkg.pr.new/@tanstack/lit-form@1963

@tanstack/preact-form

npm i https://pkg.pr.new/@tanstack/preact-form@1963

@tanstack/react-form

npm i https://pkg.pr.new/@tanstack/react-form@1963

@tanstack/react-form-devtools

npm i https://pkg.pr.new/@tanstack/react-form-devtools@1963

@tanstack/react-form-nextjs

npm i https://pkg.pr.new/@tanstack/react-form-nextjs@1963

@tanstack/react-form-remix

npm i https://pkg.pr.new/@tanstack/react-form-remix@1963

@tanstack/react-form-start

npm i https://pkg.pr.new/@tanstack/react-form-start@1963

@tanstack/solid-form

npm i https://pkg.pr.new/@tanstack/solid-form@1963

@tanstack/solid-form-devtools

npm i https://pkg.pr.new/@tanstack/solid-form-devtools@1963

@tanstack/svelte-form

npm i https://pkg.pr.new/@tanstack/svelte-form@1963

@tanstack/vue-form

npm i https://pkg.pr.new/@tanstack/vue-form@1963

commit: 3f1f8ee

@codecov
Copy link
Copy Markdown

codecov Bot commented Dec 31, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 97.10%. Comparing base (6892ed0) to head (3f1f8ee).
⚠️ Report is 202 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1963      +/-   ##
==========================================
+ Coverage   90.35%   97.10%   +6.74%     
==========================================
  Files          38        2      -36     
  Lines        1752       69    -1683     
  Branches      444        4     -440     
==========================================
- Hits         1583       67    -1516     
+ Misses        149        2     -147     
+ Partials       20        0      -20     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@imramkrishna
Copy link
Copy Markdown
Contributor Author

CI is green and coverage is fixed.
This PR is ready for review.

@crutchcorn
Copy link
Copy Markdown
Member

Wait, I'm confused - shouldn't this be avoided as-of https://github.com/TanStack/form/releases/tag/%40tanstack%2Fvue-form%401.27.7 ?

@imramkrishna
Copy link
Copy Markdown
Contributor Author

I checked the 1.27.7 changes and this does not reintroduce the behavior avoided there.

The change in 1.27.7 avoids subscribing to the full field state to prevent unnecessary updates.
This PR follows the same intent by narrowing subscriptions further in array mode, limiting value tracking to length-only while still respecting the current store architecture.

If there’s a specific concern with multiple useStore subscriptions in Vue, I’m happy to adjust the approach.

@msanchezdev
Copy link
Copy Markdown

I see this mentions Vue but the behavior also happens in react

@mkeyy0
Copy link
Copy Markdown

mkeyy0 commented Jan 19, 2026

@crutchcorn Yeah, it should be avoided, but while it is fixed in React, in Vue, it still re-renders each field after each change in any array field that significantly affects the performance. You could see the repro from the linked issue

# Conflicts:
#	packages/vue-form/src/useField.tsx
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 10, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8b4ce306-68f3-407f-92ca-2d80da2997b5

📥 Commits

Reviewing files that changed from the base of the PR and between 666343b and 3f1f8ee.

📒 Files selected for processing (1)
  • packages/vue-form/src/useField.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/vue-form/src/useField.tsx

📝 Walkthrough

Walkthrough

useField now selects only needed store slices (value and granular meta fields), builds a computed fieldState exposing { value, meta }, and tracks array mode by observing value.length. A test was added to validate mode="array" behavior.

Changes

Array Field Re-render Optimization

Layer / File(s) Summary
State Selection and Computed Refs
packages/vue-form/src/useField.tsx
Import computed. Replace a single full-store useStore selector with separate selectors for value (tracking value.length when mode='array') and individual meta flags; assemble a computed fieldState { value, meta }.
API Wrapper / Return Update
packages/vue-form/src/useField.tsx
Wrap fieldApi in a computed extendedFieldApi whose state getter returns the computed fieldState; return { api: extendedFieldApi.value, state: fieldState.value }.
Field Slot Prop Update
packages/vue-form/src/useField.tsx
Field component slot now passes state: fieldApi.state (computed) instead of fieldApi.state.value.
Array Mode Test Coverage
packages/vue-form/tests/useField.test.tsx
Add test for form.Field with mode="array" that asserts initial rendered value and that field.pushValue updates the rendered JSON array.
Docs Updates
docs/framework/vue/guides/arrays.md
Add mode="array" to people field in Basic Usage and Full Example documentation snippets.
Example App Update
examples/vue/array/src/App.vue
Set mode="array" on the people form.Field in the example application.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant FieldComponent
  participant FieldStore
  Client->>FieldComponent: render field (mode="array")
  FieldComponent->>FieldStore: subscribe to value.length + meta flags
  Client->>FieldComponent: call field.pushValue("b")
  FieldComponent->>FieldStore: push value -> updates value length
  FieldStore->>FieldComponent: notify (length change)
  FieldComponent->>Client: re-render limited to array-length-aware parts
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰
I nibble only bits the field must show,
Count the hops—just length—to keep things slow.
Meta carrots plucked one by one, not all,
Now renders whisper, not a thundering call.
Hooray—lightfoot forms and fewer stalls!

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is incomplete, cutting off mid-sentence without explaining the solution or including the required checklist items from the template. Complete the description by explaining the implemented solution, addressing the subscription optimization approach, and filling out all required checklist items from the template.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: preventing unnecessary full array re-renders by implementing array mode optimizations in the Vue adapter.
Linked Issues check ✅ Passed The code changes implement the core objective from #1961 by narrowing field state subscriptions in array mode to track only array length, preventing full array re-renders when individual elements change.
Out of Scope Changes check ✅ Passed All changes directly support the array re-render fix: useField.tsx implements subscription optimization, tests verify array mode functionality, and documentation/examples demonstrate the mode attribute usage.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/vue-form/tests/useField.test.tsx (1)

405-431: ⚡ Quick win

Add an explicit “no sibling rerender” regression assertion.

This test confirms pushValue updates value, but it doesn’t verify the core performance guarantee from the issue. Consider adding a render-count spy to assert that editing one array item does not rerender unaffected siblings in mode="array".

Suggested test pattern
+ const siblingRenderSpy = vi.fn()
...
+ {field.state.value.map((_, i) => (
+   <form.Field key={i} name={`test[${i}]`}>
+     {({ field: subField }: { field: AnyFieldApi }) => {
+       if (i === 1) siblingRenderSpy()
+       return <input value={subField.state.value} ... />
+     }}
+   </form.Field>
+ ))}
...
+ const before = siblingRenderSpy.mock.calls.length
+ await user.type(firstInput, 'x')
+ expect(siblingRenderSpy.mock.calls.length).toBe(before)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/vue-form/tests/useField.test.tsx` around lines 405 - 431, The test
is missing an assertion that pushing into an array does not rerender unaffected
siblings; add a render-count spy by rendering each array item as its own small
component (e.g., ItemA and ItemB inside the form.Field render) that increments a
visible counter or calls a jest.fn on each render, then call
field.pushValue('b') and assert the newly pushed item renders but the
pre-existing sibling's render count did not increase; locate the Comp component
and the form.Field/mode="array" usage and add the per-item render counters and
expectations around field.pushValue to assert no sibling rerender.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/vue-form/tests/useField.test.tsx`:
- Around line 405-431: The test is missing an assertion that pushing into an
array does not rerender unaffected siblings; add a render-count spy by rendering
each array item as its own small component (e.g., ItemA and ItemB inside the
form.Field render) that increments a visible counter or calls a jest.fn on each
render, then call field.pushValue('b') and assert the newly pushed item renders
but the pre-existing sibling's render count did not increase; locate the Comp
component and the form.Field/mode="array" usage and add the per-item render
counters and expectations around field.pushValue to assert no sibling rerender.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 215eaf90-3766-45a2-812e-d4c3255e200c

📥 Commits

Reviewing files that changed from the base of the PR and between 583741b and d0f4a12.

📒 Files selected for processing (2)
  • packages/vue-form/src/useField.tsx
  • packages/vue-form/tests/useField.test.tsx

@crutchcorn
Copy link
Copy Markdown
Member

Good catch, all. I've updated the code to reflect the React adapter better and updated the Vue array docs to include mode. Merging now. Might wait a moment to release so I can look at the other adapters and try to fix there as well.

Thanks for your patience with us!

@crutchcorn crutchcorn merged commit 452b103 into TanStack:main May 10, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Entrire array elements are re-rendered when updating one array element

4 participants