-
Notifications
You must be signed in to change notification settings - Fork 46.6k
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
Fix uncontrolled radios #10156
Fix uncontrolled radios #10156
Conversation
fixtures/dom/public/react-loader.js
Outdated
REACT_PATH = 'https://unpkg.com/react@' + version + '/dist/react.min.js'; | ||
DOM_PATH = 'https://unpkg.com/react-dom@' + version + '/dist/react-dom.min.js'; | ||
REACT_PATH = 'https://unpkg.com/react@' + version + '/dist/react.js'; | ||
DOM_PATH = 'https://unpkg.com/react-dom@' + version + '/dist/react-dom.js'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I switched to the unminified versions intentionally so was easier to step through stuff in devtools
@@ -860,6 +860,9 @@ ReactDOMComponent.Mixin = { | |||
// happen after `_updateDOMProperties`. Otherwise HTML5 input validations | |||
// raise warnings and prevent the new value from being assigned. | |||
ReactDOMInput.updateWrapper(this); | |||
// We also check that we haven't missed a value update, such as a | |||
// Radio group shifting the checked value to another named radio input. | |||
inputValueTracking.updateValueIfChanged(this); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the actual fix
function attachTracker(inst: InstanceWithWrapperState, tracker: ?ValueTracker) { | ||
inst._wrapperState.valueTracker = tracker; | ||
function detachTracker(subject: SubjectWithWrapperState) { | ||
delete subject._wrapperState.valueTracker; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we use = null
instead? delete
causes deopts in my experience.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think so. I think originally I was being overly cautious about expected properties sticking around.
); | ||
|
||
var node = inst; | ||
if (!(inst: any).nodeName) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you comment on this part? What are you trying to detect?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah its not great, I'm trying to detect if inst
is a component instance, like it is in stack, or a DOM node as in fiber. This is the most minimal DRY change I could think of. Alternatively we could split this method into two, but its a bunch of duplication.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's use same check as in here and leave a TODO to remove it when Stack is removed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
perfect 👍
Looks like Stack tests are failing
Also prettier is unhappy. |
not sure what the deal with prettier is, i ran it locally. lemme mess around with it. Do I have to do something special to run tests against the stack renderer? |
o nvm on the failing test, that's my bad. sorry |
@jquense This looks great from my end. I'd be happy to take on some manual browser testing. What should I test for? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Aside from the reference error (covered by lint), this checks out in:
- IE11
- IE15
- Chrome 59
- Safari 10
- Firefox 54
!version || !resolvedIn || semver.gte(version, resolvedIn); | ||
|
||
complete = !isTestRelevant || complete; | ||
complete = !isTestFixed || complete; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doing some local testing, looks like this causes a failure on line 103:
https://github.com/jquense/react/blob/15228e8b5f9d056d475955a54723cd906ffa5619/fixtures/dom/src/components/TestCase.js#L103
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Last time I do merge conflict resolve on github :P man.
Here's a test build with the lint fix: |
I don't think this change could change behavior for controlled inputs but technically it's doing slightly more work on every input update. I did run the various fixtures for input changes and didn't see anything but just noting it for posterity. |
Checks out for me. @jquense Could you handle bringing this over to 15.x? |
AFAIK @flarnie's not working on 15.6.x right now. Do you want to handle this release? Would be your first release :-) |
It's yours! Look at what we did for previous releases: merge things to a branch freshly based of 15-stable, let me know when it's ready and we can get it out. |
} | ||
|
||
function getValueFromNode(node) { | ||
function getValueFromNode(node: any) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In general we should avoid adding any
wherever possible. It very often leads to bugs. I know we used it sometimes, but I just want to point out for future reviews that it should be used only as last measure.
(I don’t mean this particular case is problematic, but this is something to always keep in mind.)
}, | ||
|
||
updateValueIfChanged(inst: InstanceWithWrapperState | Fiber) { | ||
if (!inst) { | ||
updateValueIfChanged(subject: SubjectWithWrapperState | Fiber) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was there any specific reason for changing terminology here? Did inputs change? Or was existing terminology inconsistent? In Stack, we used instance
for internal instance, but in Fiber we use it for abstract renderer-specific instance (such as DOM node in case of ReactDOM). So both seemed fitting to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The inputs changed as far as I can know. The only place this was called otherwise was in the ChangeEventPlugin where it's just called an "instance" It may very well be a DOM node there in Fiber (now that I think of it). The need for the logic branch here is that we need the actual DOM node, and to handle both renderers that means calling getNodeFromInstance
which seems to be fairly unforgiving of you passing in a node already, which I think is somerhow what's happening
As for the terminology it was more to save line space instead of writing InstanceWithWrapperState | ElementWithWrapperState
in a few places.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do I understand correctly that now the code supports passing three different types? Stack instance, Fiber, and a DOM node.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TBH I don't know what the Fiber
type is/does here. I didn't add the types originally and AFAICT its never passed a Fiber, only an instance or Dom node
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm. I was under impression it was always passed a Fiber or a Stack instance before this change. Since .tag
check is for detecting Fibers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That may be! I still unfamiliar with the Fiber data types but the only place this is called is is ChangeEventPlugin with the targetInst
parameter and DOMComponent. I'm unsure what the case is in the changeEventPlugin.
Is the Fiber situation that sometimes it may be an "instance" (DOM Node) and sometimes it may be a "Fiber". In the Stack case it's always an internal Instance
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll check that, thanks.
|
||
// TODO: remove check when the Stack renderer is retired | ||
if ((subject: any).nodeType !== ELEMENT_NODE) { | ||
node = ReactDOMComponentTree.getNodeFromInstance(subject); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you explain more about why this change is necessary? I still don’t quite get why it was called unconditionally, but now is called conditionally, even though subject
(aka inst
) type has not changed (or has it?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops, I misread. I now see that previous code also had (a different) check. I wonder if changing that check is what caused the issue. I'm still not sure why though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm no I didn’t misread 😛
This does look like a new check. So my previous comment still stands.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
more in the comment above, but the check is purely to avoid calling getNodeFromInstance
when inst
is already a Node, as in the Fiber case. The only reason it was added was to handle both renderers, You can see in backport PR that this file isn't even touched (i'm guessing that one probably doesn't suffer this bug btw)
Fixes #9988, this is against master but we should cherry-pick the change back to 15.6 for a patch release.
I added a fixture instead of a test because, writing a unit test that reproduced the issue was confusingly hard actually.
Most of the extra bits here are to handle both fiber and stack, the content of the PR is really the one line in DOMComponent.