diff --git a/fixtures/dom/src/components/fixtures/selects/index.js b/fixtures/dom/src/components/fixtures/selects/index.js index ddf5d735fc..b89e63314b 100644 --- a/fixtures/dom/src/components/fixtures/selects/index.js +++ b/fixtures/dom/src/components/fixtures/selects/index.js @@ -1,10 +1,34 @@ const React = window.React; +const ReactDOM = window.ReactDOM; class SelectFixture extends React.Component { state = {value: ''}; + _nestedDOMNode = null; + onChange = event => { this.setState({value: event.target.value}); }; + + componentDidMount() { + this._renderNestedSelect(); + } + + componentDidUpdate() { + this._renderNestedSelect(); + } + + _renderNestedSelect() { + ReactDOM.render( + , + this._nestedDOMNode + ); + } + render() { return (
@@ -24,8 +48,16 @@ class SelectFixture extends React.Component { - + + + +
+ Controlled in nested subtree +
(this._nestedDOMNode = node)} /> + + This should synchronize in both direction with the one above. +
); diff --git a/src/renderers/dom/shared/eventPlugins/ChangeEventPlugin.js b/src/renderers/dom/shared/eventPlugins/ChangeEventPlugin.js index 6381eb54ed..ba1d7b0461 100644 --- a/src/renderers/dom/shared/eventPlugins/ChangeEventPlugin.js +++ b/src/renderers/dom/shared/eventPlugins/ChangeEventPlugin.js @@ -47,16 +47,21 @@ function shouldUseChangeEvent(elem) { ); } -function createAndAccumulateChangeEvent(inst, nativeEvent, target) { +function createAndAccumulateChangeEvent( + inst, + nativeEvent, + nativeEventTarget, + virtualTarget, +) { var event = SyntheticEvent.getPooled( eventTypes.change, inst, nativeEvent, - target, + nativeEventTarget, ); event.type = 'change'; // Flag this event loop as needing state restore. - ReactControlledComponent.enqueueStateRestore(target); + ReactControlledComponent.enqueueStateRestore(virtualTarget); EventPropagators.accumulateTwoPhaseDispatches(event); return event; } @@ -175,6 +180,7 @@ var ChangeEventPlugin = { inst, nativeEvent, nativeEventTarget, + targetNode, ); return event; } diff --git a/src/renderers/dom/shared/wrappers/__tests__/ReactDOMSelect-test.js b/src/renderers/dom/shared/wrappers/__tests__/ReactDOMSelect-test.js index 5aa28eff34..95c547b7b9 100644 --- a/src/renderers/dom/shared/wrappers/__tests__/ReactDOMSelect-test.js +++ b/src/renderers/dom/shared/wrappers/__tests__/ReactDOMSelect-test.js @@ -589,4 +589,68 @@ describe('ReactDOMSelect', () => { expect(node.options[1].selected).toBe(true); // b expect(node.options[2].selected).toBe(false); // c }); + + it('should allow controlling `value` in a nested render', () => { + var selectNode; + + class Parent extends React.Component { + state = { + value: 'giraffe', + }; + + componentDidMount() { + this._renderNested(); + } + + componentDidUpdate() { + this._renderNested(); + } + + _handleChange(event) { + this.setState({value: event.target.value}); + } + + _renderNested() { + ReactDOM.render( + , + this._nestingContainer, + ); + } + + render() { + return
(this._nestingContainer = n)} />; + } + } + + var container = document.createElement('div'); + + document.body.appendChild(container); + + ReactDOM.render(, container); + + expect(selectNode.value).toBe('giraffe'); + + selectNode.value = 'gorilla'; + + let nativeEvent = document.createEvent('Event'); + nativeEvent.initEvent('input', true, true); + selectNode.dispatchEvent(nativeEvent); + + expect(selectNode.value).toEqual('gorilla'); + + nativeEvent = document.createEvent('Event'); + nativeEvent.initEvent('change', true, true); + selectNode.dispatchEvent(nativeEvent); + + expect(selectNode.value).toEqual('gorilla'); + + document.body.removeChild(container); + }); });