mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Use the virtual target for change events to avoid restoring controlled state on the real target (#10444)
* Add test for nested controlled selects The failure repros only when two events are fired sequentially, where the second event is what updates the value. * Add failure case to DOM select fixture * Use virtual event target for change event instead of native target Ensures that we use the same node to make decisions instead of two like we do now. This will cause restore to be called or not called consistently. * Pass native event target to the event We normally pass the native event target to the event object. This ensures that we do the same thing here. We still want to schedule a "restore" on the virtual target though since those are the only nodes known to React.
This commit is contained in:
committed by
GitHub
parent
2cd0ecdaac
commit
3bc64327f0
@@ -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(
|
||||
<select value={this.state.value} onChange={this.onChange}>
|
||||
<option value="">Select a color</option>
|
||||
<option value="red">Red</option>
|
||||
<option value="blue">Blue</option>
|
||||
<option value="green">Green</option>
|
||||
</select>,
|
||||
this._nestedDOMNode
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<form>
|
||||
@@ -24,8 +48,16 @@ class SelectFixture extends React.Component {
|
||||
<option value="">Select a color</option>
|
||||
<option value="red">Red</option>
|
||||
<option value="blue">Blue</option>
|
||||
<option value="gree">Green</option>
|
||||
<option value="green">Green</option>
|
||||
</select>
|
||||
<span className="hint" />
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>Controlled in nested subtree</legend>
|
||||
<div ref={node => (this._nestedDOMNode = node)} />
|
||||
<span className="hint">
|
||||
This should synchronize in both direction with the one above.
|
||||
</span>
|
||||
</fieldset>
|
||||
</form>
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
<select
|
||||
onChange={this._handleChange.bind(this)}
|
||||
ref={n => (selectNode = n)}
|
||||
value={this.state.value}>
|
||||
<option value="monkey">A monkey!</option>
|
||||
<option value="giraffe">A giraffe!</option>
|
||||
<option value="gorilla">A gorilla!</option>
|
||||
</select>,
|
||||
this._nestingContainer,
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return <div ref={n => (this._nestingContainer = n)} />;
|
||||
}
|
||||
}
|
||||
|
||||
var container = document.createElement('div');
|
||||
|
||||
document.body.appendChild(container);
|
||||
|
||||
ReactDOM.render(<Parent />, 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);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user