Skip to content

[v2] Perf: Use one atom subscription per Field - #2358

Merged
LeCarbonator merged 3 commits into
TanStack:alphafrom
scttcper:scttcper/field-subscriptions
Sep 1, 2026
Merged

[v2] Perf: Use one atom subscription per Field#2358
LeCarbonator merged 3 commits into
TanStack:alphafrom
scttcper:scttcper/field-subscriptions

Conversation

@scttcper

@scttcper scttcper commented Aug 24, 2026

Copy link
Copy Markdown

Each React Field subscribed to the same atom twice, once for value and once for meta. The field atom already preserves state identity when neither changes, so use one whole-state subscription instead.

This cuts active subscriptions from two to one per field with the same render behavior.

Summary by CodeRabbit

  • Bug Fixes

    • Improved form field subscription handling so updates are tracked consistently.
    • Reduced redundant subscriptions while preserving existing field API behavior.
    • Improved subscription efficiency for more predictable form updates.
  • Tests

    • Updated coverage to verify that each field uses a single active subscription.
    • Clarified subscription behavior in field-context tests.

Each React Field subscribed to the same atom once for value and again for meta. The field atom already preserves state identity when neither changes, so one whole-state subscription has the same render behavior.

Co-Authored-By: OpenAI Codex <noreply@openai.com>
@coderabbitai

coderabbitai Bot commented Aug 24, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Team

Run ID: 99f01490-c0b1-478f-b86f-a445f67f2106

📥 Commits

Reviewing files that changed from the base of the PR and between 2257d3d and 4fde2fb.

📒 Files selected for processing (1)
  • packages/react-form/src/ReactForm/fieldSubscriptions.lib.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/react-form/src/ReactForm/fieldSubscriptions.lib.ts

Included review availability: Your plan provides up to 8 included reviews per hour; 7 remain after this review.


📝 Walkthrough

Walkthrough

The field subscription now observes the complete field state through one subscription. The field-context test description and expected active subscription count reflect this change.

Changes

Field subscription update

Layer / File(s) Summary
Unify field state subscription
packages/react-form/src/ReactForm/fieldSubscriptions.lib.ts, packages/react-form/tests/FormGroup.spec.tsx
useValueFieldSubscription subscribes to full field state and tracks that state for memoization. The test expects one active AppForm field-provider subscription.

Estimated code review effort: 2 (Simple) | ~10 minutes

Merge Risk: ⚪ Minimal · up to 4fde2

This localized change reduces each field from two atom subscriptions to one without supplied evidence of altered render behavior or other merge-blocking impact; no actionable merge-blocking risk remains beyond normal checks and review.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description clearly explains the subscription reduction and its motivation, but it omits the required "## 🎯 Changes", "## ✅ Checklist", and "## 🚀 Release Impact" sections, including checklist and … Add the required template headings. Complete the contributing-guide and test checklist items. State whether the change affects published code and add a changeset if required, or confirm that it is docs/CI/dev-only.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 2 functions across 1 files. 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 clearly and concisely identifies the performance change: reducing atom subscriptions to one per field.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Full details: Description check

Explanation

The description clearly explains the subscription reduction and its motivation, but it omits the required "## 🎯 Changes", "## ✅ Checklist", and "## 🚀 Release Impact" sections, including checklist and changeset status.

  • Fix all pre-merge checks with AI
✨ 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.

@scttcper
scttcper marked this pull request as ready for review August 24, 2026 17:35
@LeCarbonator

Copy link
Copy Markdown
Contributor

You mention performance for the PR. Do you have some rough numbers for it? Memory / runtime?

@scttcper

Copy link
Copy Markdown
Author

@LeCarbonator all the perf is just from going from 2 to 1

I ran a focused benchmark using the exact before/after hooks with production React 19.2.8. Each variant ran in a fresh process 10 times, alternating order; results are medians.

1,000 mounted fields

Metric Before After Change
Active subscriptions 2,000 1,000 −50%
Retained heap 7.50 MiB 4.39 MiB −41.5%
Mount time 5.39 ms 4.99 ms −7.4%
Update all fields 1.08 ms 0.64 ms −40.1%
Update one field 4.8µs 4.4µs −9%
Renders per update 1 1 No change

The harness mounts non-array fields backed by individual TanStack Store atoms, measures synchronous mount/update work, and measures retained heap after forced GC.

This isolates subscription overhead rather than measuring a complete application, so the absolute timings are directional. The consistent result is half the subscriptions and callbacks, with roughly 3.2 KiB less retained heap per mounted field.

@crutchcorn

Copy link
Copy Markdown
Member

Sorry, don't have a ton of cycles here to respond more in depth, but I'd attempted this in v1 and benchmarks showed MASSIVE improvements then (like 10x), but the UX of 1000 mounted fields slowed to a crawl for reasons I couldn't identify.

Might be helpful for us to all sit down and discuss and on our end manually test.

Not saying that this is a bad PR or approach, just flagging that benchmarks for this has been directionally wrong before.

@scttcper

Copy link
Copy Markdown
Author

Let sol on ultra try to figure out what you were referring to, obviously feel free to review/close whatever.

Model used: OpenAI Codex — GPT-5.6 Sol

Codex investigated the relevant v1 history and benchmarked the exact base and PR commits in production Chromium.

The closest v1 match is PR #2036. That implementation replaced seven subscriptions with one selector that created a new seven-property object and used a custom comparator. It was also mixed with substantial core store and notification changes, so the historical results do not isolate subscription consolidation as the cause. Its “1,000-field” benchmarks actually rendered 100 fields in jsdom and did not measure browser rendering or memory.

V2 has a different design. Before this PR, a normal field subscribes separately to state.value and state.meta. The PR subscribes once to the existing {value, meta} snapshot. V2 core preserves that snapshot’s identity whenever both references are unchanged:

if (prev?.meta === meta && prev.value === value) {
  return prev
}

Therefore, the rerender conditions are equivalent:

before: value changed OR meta changed
after:  field snapshot changed

The PR does not create a projected object, add a custom comparator, or change core notification behavior. ArrayField is also untouched.

Real-browser confirmation with 1,000 controlled fields and 60 fresh contexts per variant found:

  • Mount: 7.5% faster
  • Typing to commit: 12.2% faster
  • Typing p95: 12.7% faster
  • JavaScript heap: 15.4% lower

@crutchcorn

Copy link
Copy Markdown
Member

@scttcper I'm an idiot and I wasted your tokens.

I just reviewed the diff quickly. For some reason I'd gotten a LOT wrong in my mind about what this PR was and assumed it was doing something more closely related to splitting one store into many for modularity. Frankly I don't know how I got things this mixed up.

So sorry.

This PR LGTM broadly, but might have weird consequences with Compiler. So long as it works well there we should merge.

Sorry again, that's embarrassing.

@scttcper

scttcper commented Aug 27, 2026

Copy link
Copy Markdown
Author

@crutchcorn work pays for it 🤷

@LeCarbonator

Copy link
Copy Markdown
Contributor

I think this should be compiler safe. Don't see how changing it from two to one could have consequences for it.

I wish I had better Wi-Fi to actually try this, but that'll have to wait until next monday.

@nx-cloud

nx-cloud Bot commented Aug 27, 2026

Copy link
Copy Markdown

View your CI Pipeline Execution ↗ for commit 2257d3d

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

☁️ Nx Cloud last updated this comment at 2026-09-01 08:03:19 UTC

@pkg-pr-new

pkg-pr-new Bot commented Aug 27, 2026

Copy link
Copy Markdown
More templates

@tanstack/angular-form

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

@tanstack/form-core

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

@tanstack/form-devtools

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

@tanstack/lit-form

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

@tanstack/preact-form

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

@tanstack/react-form

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

@tanstack/react-form-devtools

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

@tanstack/react-form-nextjs

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

@tanstack/react-form-start

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

@tanstack/solid-form

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

@tanstack/solid-form-devtools

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

@tanstack/svelte-form

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

@tanstack/vue-form

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

commit: 4fde2fb

@codecov-commenter

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

✅ All modified and coverable lines are covered by tests.
⚠️ Please upload report for BASE (alpha@4ad5a7b). Learn more about missing BASE report.
❗ Your organization needs to install the Codecov GitHub app to enable full functionality.

Additional details and impacted files
@@           Coverage Diff            @@
##             alpha    #2358   +/-   ##
========================================
  Coverage         ?   92.91%           
========================================
  Files            ?       14           
  Lines            ?      240           
  Branches         ?       19           
========================================
  Hits             ?      223           
  Misses           ?       16           
  Partials         ?        1           

☔ View full report in Codecov by Harness.
📢 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.

@LeCarbonator LeCarbonator left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM!

Comment thread packages/react-form/src/ReactForm/fieldSubscriptions.lib.ts Outdated
@LeCarbonator
LeCarbonator merged commit 5f4de2c into TanStack:alpha Sep 1, 2026
9 checks passed
@scttcper
scttcper deleted the scttcper/field-subscriptions branch September 1, 2026 14:20
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.

4 participants