diff --git a/fixtures/dom/src/components/fixtures/input-change-events/RadioNameChangeFixture.js b/fixtures/dom/src/components/fixtures/input-change-events/RadioNameChangeFixture.js new file mode 100644 index 0000000000000..1169c61c2d77f --- /dev/null +++ b/fixtures/dom/src/components/fixtures/input-change-events/RadioNameChangeFixture.js @@ -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 ( +
+ + + + +
+ +
+
+ ); + } +} + +export default RadioNameChangeFixture; diff --git a/fixtures/dom/src/components/fixtures/input-change-events/index.js b/fixtures/dom/src/components/fixtures/input-change-events/index.js index 41920e5802403..70764989627aa 100644 --- a/fixtures/dom/src/components/fixtures/input-change-events/index.js +++ b/fixtures/dom/src/components/fixtures/input-change-events/index.js @@ -5,6 +5,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'; class InputChangeEvents extends React.Component { @@ -87,6 +88,24 @@ class InputChangeEvents extends React.Component { + + +
  • Click the toggle button
  • +
    + + + The checked radio button should switch between the first and second radio button + + + +
    ); } diff --git a/packages/react-dom/src/__tests__/ReactDOMInput-test.js b/packages/react-dom/src/__tests__/ReactDOMInput-test.js index e968482651e87..31d1a3abda1d9 100644 --- a/packages/react-dom/src/__tests__/ReactDOMInput-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMInput-test.js @@ -669,6 +669,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 ( +
    +
    + ); + } + } + + var stub = ReactTestUtils.renderIntoDocument(); + 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'); diff --git a/packages/react-dom/src/client/ReactDOMFiberComponent.js b/packages/react-dom/src/client/ReactDOMFiberComponent.js index 7919058472796..c77e29a4d63c6 100644 --- a/packages/react-dom/src/client/ReactDOMFiberComponent.js +++ b/packages/react-dom/src/client/ReactDOMFiberComponent.js @@ -771,6 +771,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. diff --git a/packages/react-dom/src/client/ReactDOMFiberInput.js b/packages/react-dom/src/client/ReactDOMFiberInput.js index bad8f1645f576..7ddc36ee0c892 100644 --- a/packages/react-dom/src/client/ReactDOMFiberInput.js +++ b/packages/react-dom/src/client/ReactDOMFiberInput.js @@ -144,6 +144,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__) { @@ -183,14 +191,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) {