fix: #8659 Number Field Input Validation Update - #10362
Conversation
|
|
||
| let button = getByTestId('submit'); | ||
| await user.click(button); | ||
| await act(async () => { |
There was a problem hiding this comment.
what's this extra act for? user event uses fireEvent which already wraps everything inside an act
Also, why are there changes in this file? using should be the correct way to use a spy.
If there's something that isn't being awaited, likely it's something leaking from an earlier test into this one.
Whatever it is though, it doesn't give me a lot of confidence in this approach, especially without any explanation.
There was a problem hiding this comment.
This was from an earlier attempt to fix CI failures caused by validation updates not being fully awaited, specifically triggering “state update outside of act” warnings.
Since userEvent already wraps in act, I initially removed the extra wrapper, but CI still surfaced warnings, so I added an explicit act() wrapper around the update to ensure stability.
I also reverted the unrelated changes here and aligned it with the existing patterns (using for spies).
| value: numberValue | ||
| }); | ||
|
|
||
| let prevControlledValue = useRef(value); |
There was a problem hiding this comment.
how does this fix work? what is the root cause of the problem? what other approaches were considered?
This comment #8659 (comment) made it sound like this was a bigger issue than just NumberField, did you consider the root of all of them for a more holistic approach?
There was a problem hiding this comment.
Thanks for reviewing! After digging deeper into #8659, I realized this is a broader issue across input types with two related root causes:
1. Stale validation on controlled changes
When a value updates externally (e.g. via a controlled prop or button), no native input/change event fires, so validation isn’t re-committed. Existing errors can remain until a blur or another commit-triggering event occurs.
2. Missing validation for programmatic constraint violations
For constraints like maxLength and minLength, the native DOM does not update its validity state when values are set programmatically (e.g. validity.tooLong remains false). This means invalid values may not surface an error or block submission.
Approaches considered:
I initially explored detecting external updates using DOM-based checks (e.g. focus detection), but this proved unreliable for composite components like DatePicker and TimeField, where internal focus changes were incorrectly treated as external updates.
Updated approach:
Based on that, I reverted the earlier useNumberFieldState changes and moved the solution into the shared validation layer:
- Added an optional
isFocusWithinprop so composite components can provide accurate focus state - Re-commit validation when values change externally to prevent stale validation state
- Ensure constraint validation reflects programmatic updates so form behavior stays consistent
I’ve pushed these updates along with the corresponding tests.
|
|
||
| let prevControlledValue = useRef(value); | ||
| useEffect(() => { | ||
| if (value !== undefined && !Object.is(value, prevControlledValue.current)) { |
There was a problem hiding this comment.
This comment (i'm not sure why it's hidden as outdated) made it sound possibly related to focus, but i don't see anything about focus here
#8659 (comment)
what did you discover when you looked into this?
There was a problem hiding this comment.
It’s not directly about focus itself.
From what I found, the issue is that validation isn’t re-committed when values change programmatically. Focus only matters in distinguishing external updates (when the field is unfocused), which is now handled in the shared validation logic as mentioned above.
…#8659) Provides a comprehensive fix to evaluate programmatic constraints and safely sync validation updates without breaking compound components.
Closes #8659
✅ Pull Request Checklist:
📝 Test Instructions:
🧢 Your Project: