-
Couldn't load subscription status.
- Fork 3k
perf: prevent form rerenders #14266
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
perf: prevent form rerenders #14266
Conversation
📦 esbuild Bundle Analysis for payloadThis analysis was generated by esbuild-bundle-analyzer. 🤖
Largest pathsThese visualization shows top 20 largest paths in the bundle.Meta file: packages/next/meta_index.json, Out file: esbuild/index.js
Meta file: packages/payload/meta_index.json, Out file: esbuild/index.js
Meta file: packages/payload/meta_shared.json, Out file: esbuild/exports/shared.js
Meta file: packages/richtext-lexical/meta_client.json, Out file: esbuild/exports/client_optimized/index.js
Meta file: packages/ui/meta_client.json, Out file: esbuild/exports/client_optimized/index.js
Meta file: packages/ui/meta_shared.json, Out file: esbuild/exports/shared_optimized/index.js
DetailsNext to the size is how much the size has increased or decreased compared with the base branch of this PR.
|
|
It's a shame that this PR is closed, this change has big system improvement potential. Unnecessary component re-rendering (even if not executing the 2nd React render phase) in large document continues to be a UI performance bottleneck in our Payload based CMS on CPU-limited clients. |
|
Yes, it's something I'd like to address at some point. The remaining solution to explore is whether it's possible to make Something like this is what this PR attempted to explore with Also, I don't know if making If there are any payload configurations that are experiencing performance issues on low-end devices, please send us a reproducible and we will investigate the best course of action to resolve it. |
Fixes #14213
Summary
Fixes unnecessary re-renders of all Form children when any field changes.
The issue was that the
Formcomponent passed a memoized array[formState, dispatchFields]to the context provider, which changed on every field update, causing all children to re-render regardless of whether they useduseFormFieldsor not.This PR replaces use-context-selector with React’s native
useSyncExternalStoreAPI and implements a stable store pattern that prevents the Provider’s value from changing, ensuring only components usinguseFormFieldswith changed selector values will re-render.Root Cause Analysis
The issue reported in #14213 was not that
useFormFieldswas causing unnecessary re-renders. The hook itself worked correctly withuse-context-selector, re-rendering only when the selected field changed.The real problem was that the entire
Formcomponent tree re-rendered on any field change because:When typing in the title field,
formStatechanges → new array[formState, dispatchFields]→ Provider's value changes → all children re-render (not just components usinguseFormFields).Why
useMemoDoesn't Solve ItThe memoized value still changes whenever
formStatechanges (which happens on every keystroke), so the Provider still receives a new value and re-renders all children.Solution: Stable Store Pattern
The fix uses
useSyncExternalStorewith a completely stable store object:Now:
useFormFieldswith selectors that return different values will re-renderChanges
useFormFieldsto watch other fieldsWhy
useSyncExternalStoreIs NecessaryThis isn’t just a modernization. it’s the only way to achieve:
Benefits