Fix/numberfield format options input reset#9905
Fix/numberfield format options input reset#9905Wonchang0314 wants to merge 4 commits intoadobe:mainfrom
Conversation
|
@Wonchang0314 thanks for the PR! Mind signing the CLA? |
There was a problem hiding this comment.
When I removed the changes made in useNumberFieldState and ran the tests, they still passed. Can you double check these and make sure they fail without the changes and pass with the fix? If that is proving difficult, feel free to add a storybook reproduction so we can more easily verify these changes,
There was a problem hiding this comment.
@LFDanLu Yes, I'm happy to sign the CLA. Should I just sign and submit the CLA form as an individual?
There was a problem hiding this comment.
@LFDanLu I found that the existing tests were not effectively covering this case, so I removed them. The issue here involves a state update triggered during render, which is not reliably reproduced in the jsdom environment.
Instead, I added tests using renderHook to directly test useNumberFieldState. I verified the behavior both before and after the fix to ensure that the issue is reproducible without the fix and resolved with it.
Thanks for taking a look!
There was a problem hiding this comment.
@Wonchang0314 Yep, you can just sign it as an individual. You might need to close and re-open the PR for it to run the check
|
|
||
| // Stabilize formatOptions reference to avoid unnecessary re-renders | ||
| // when consumers pass inline object literals with the same content. | ||
| let formatOptionsRef = useRef(formatOptions); |
There was a problem hiding this comment.
seems like the lint is breaking here due to this change: Reading from refs during rendering is not allowed.
There was a problem hiding this comment.
Correct, this is not allowed as it can break React concurrency
| }; | ||
| } | ||
|
|
||
| function isEqualFormatOptions(a: Intl.NumberFormatOptions | undefined, b: Intl.NumberFormatOptions | undefined) { |
There was a problem hiding this comment.
mind adding a comment about this shallow equality equaling a deep equality bc Intl.NumberFormatOptions are primitives? think it would be helpful for future reference
There was a problem hiding this comment.
maybe we should update the equality check here instead?
There was a problem hiding this comment.
I agree with your suggestion to update the equality check here. I've applied the fix and pushed the changes.
fe21549 to
885f69d
Compare
Closes #1893
Related to #9899
Summary
When formatOptions is passed as an inline object literal (e.g. formatOptions={{ style: 'currency',
currency: 'USD' }}), every parent re-render creates a new object reference. This caused
useNumberFieldState to detect a "change" in formatOptions and overwrite the current inputValue with
the last committed number value — resetting whatever the user was typing mid-input.
This fix stabilizes the formatOptions reference inside useNumberFieldState using a useRef and a
shallow equality check. If the new formatOptions has the same key-value pairs as the previous one,
the old reference is reused, preventing a spurious inputValue reset. I though this approach is appropriate
because all values in Intl.NumberFormatOptions are primitives (strings, numbers, booleans), so
shallow equality is equivalent to deep equality.
✅ Pull Request Checklist:
📝 Test Instructions:
yarn jest packages/react-aria-components/test/NumberField.test.js
yarn jest packages/@adobe/react-spectrum/test/numberfield/NumberField.test.js
- "does not reset input value when parent re-renders with same formatOptions content" — a parent
component with its own state re-renders while formatOptions is an inline object literal; the typed
value must be preserved.
- "does not reset input during typing when re-rendered with same formatOptions content" — same
scenario via the rerender helper.
}} as an inline literal inside a component that re-renders on some other state change. Type a value
without committing (no blur/Enter) and trigger the parent re-render — the typed value should no
longer be reset.
🧢 Your Project: