mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Switching the name property preserves radio selection
Fixes a case where changing the name and checked value of a radio button in the same update would lead to checking the wrong radio input. Also adds a DOM test fixture for related issue. Related issues: https://github.com/facebook/react/issues/7630
This commit is contained in:
committed by
Nathan Hunzaker
parent
3f405da614
commit
70abda5b92
@@ -0,0 +1,46 @@
|
||||
const React = window.React;
|
||||
const noop = n => n;
|
||||
|
||||
class RadioNameChangeFixture extends React.Component {
|
||||
state = {
|
||||
updated: false,
|
||||
};
|
||||
onClick = () => {
|
||||
this.setState(state => {
|
||||
return {updated: !state.updated};
|
||||
});
|
||||
};
|
||||
render() {
|
||||
const {updated} = this.state;
|
||||
const radioName = updated ? 'firstName' : 'secondName';
|
||||
return (
|
||||
<div>
|
||||
<label>
|
||||
<input
|
||||
type="radio"
|
||||
name={radioName}
|
||||
onChange={noop}
|
||||
checked={updated === true}
|
||||
/>
|
||||
First Radio
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<input
|
||||
type="radio"
|
||||
name={radioName}
|
||||
onChange={noop}
|
||||
checked={updated === false}
|
||||
/>
|
||||
Second Radio
|
||||
</label>
|
||||
|
||||
<div>
|
||||
<button type="button" onClick={this.onClick}>Toggle</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default RadioNameChangeFixture;
|
||||
@@ -3,6 +3,7 @@ import TestCase from '../../TestCase';
|
||||
import RangeKeyboardFixture from './RangeKeyboardFixture';
|
||||
import RadioClickFixture from './RadioClickFixture';
|
||||
import RadioGroupFixture from './RadioGroupFixture';
|
||||
import RadioNameChangeFixture from './RadioNameChangeFixture';
|
||||
import InputPlaceholderFixture from './InputPlaceholderFixture';
|
||||
const React = window.React;
|
||||
|
||||
@@ -88,6 +89,24 @@ class InputChangeEvents extends React.Component {
|
||||
|
||||
<InputPlaceholderFixture />
|
||||
</TestCase>
|
||||
<TestCase
|
||||
title="Radio button groups with name changes"
|
||||
description={`
|
||||
A radio button group should have correct checked value when
|
||||
the names changes
|
||||
`}
|
||||
resolvedBy="#11227"
|
||||
affectedBrowsers="IE9+">
|
||||
<TestCase.Steps>
|
||||
<li>Click the toggle button</li>
|
||||
</TestCase.Steps>
|
||||
|
||||
<TestCase.ExpectedResult>
|
||||
The checked radio button should switch between the first and second radio button
|
||||
</TestCase.ExpectedResult>
|
||||
|
||||
<RadioNameChangeFixture />
|
||||
</TestCase>
|
||||
</FixtureSet>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -668,6 +668,45 @@ describe('ReactDOMInput', () => {
|
||||
expect(cNode.checked).toBe(true);
|
||||
});
|
||||
|
||||
it('should check the correct radio when the selected name moves', () => {
|
||||
class App extends React.Component {
|
||||
state = {
|
||||
updated: false,
|
||||
};
|
||||
onClick = () => {
|
||||
this.setState({updated: true});
|
||||
};
|
||||
render() {
|
||||
const {updated} = this.state;
|
||||
const radioName = updated ? 'secondName' : 'firstName';
|
||||
return (
|
||||
<div>
|
||||
<button type="button" onClick={this.onClick} />
|
||||
<input
|
||||
type="radio"
|
||||
name={radioName}
|
||||
onChange={emptyFunction}
|
||||
checked={updated === true}
|
||||
/>
|
||||
<input
|
||||
type="radio"
|
||||
name={radioName}
|
||||
onChange={emptyFunction}
|
||||
checked={updated === false}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
var stub = ReactTestUtils.renderIntoDocument(<App />);
|
||||
var buttonNode = ReactDOM.findDOMNode(stub).childNodes[0];
|
||||
var firstRadioNode = ReactDOM.findDOMNode(stub).childNodes[1];
|
||||
expect(firstRadioNode.checked).toBe(false);
|
||||
ReactTestUtils.Simulate.click(buttonNode);
|
||||
expect(firstRadioNode.checked).toBe(true);
|
||||
});
|
||||
|
||||
it('should control radio buttons if the tree updates during render', () => {
|
||||
var sharedParent = document.createElement('div');
|
||||
var container1 = document.createElement('div');
|
||||
|
||||
@@ -764,6 +764,17 @@ export function updateProperties(
|
||||
lastRawProps: Object,
|
||||
nextRawProps: Object,
|
||||
): void {
|
||||
// Update checked *before* name.
|
||||
// In the middle of an update, it is possible to have multiple checked.
|
||||
// When a checked radio tries to change name, browser makes another radio's checked false.
|
||||
if (
|
||||
tag === 'input' &&
|
||||
nextRawProps.type === 'radio' &&
|
||||
nextRawProps.name != null
|
||||
) {
|
||||
ReactDOMFiberInput.updateChecked(domElement, nextRawProps);
|
||||
}
|
||||
|
||||
var wasCustomComponentTag = isCustomComponent(tag, lastRawProps);
|
||||
var isCustomComponentTag = isCustomComponent(tag, nextRawProps);
|
||||
// Apply the diff.
|
||||
|
||||
+9
-8
@@ -142,6 +142,14 @@ export function initWrapperState(element: Element, props: Object) {
|
||||
};
|
||||
}
|
||||
|
||||
export function updateChecked(element: Element, props: Object) {
|
||||
var node = ((element: any): InputWithWrapperState);
|
||||
var checked = props.checked;
|
||||
if (checked != null) {
|
||||
DOMPropertyOperations.setValueForProperty(node, 'checked', checked);
|
||||
}
|
||||
}
|
||||
|
||||
export function updateWrapper(element: Element, props: Object) {
|
||||
var node = ((element: any): InputWithWrapperState);
|
||||
if (__DEV__) {
|
||||
@@ -181,14 +189,7 @@ export function updateWrapper(element: Element, props: Object) {
|
||||
}
|
||||
}
|
||||
|
||||
var checked = props.checked;
|
||||
if (checked != null) {
|
||||
DOMPropertyOperations.setValueForProperty(
|
||||
node,
|
||||
'checked',
|
||||
checked || false,
|
||||
);
|
||||
}
|
||||
updateChecked(element, props);
|
||||
|
||||
var value = props.value;
|
||||
if (value != null) {
|
||||
|
||||
Reference in New Issue
Block a user