Skip to content

Commit

Permalink
Cleanup and bug fixes for merge.
Browse files Browse the repository at this point in the history
  • Loading branch information
jim committed Apr 5, 2016
1 parent 574328a commit fef7028
Show file tree
Hide file tree
Showing 5 changed files with 133 additions and 51 deletions.
19 changes: 18 additions & 1 deletion src/renderers/dom/client/wrappers/ReactDOMInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ var ReactDOMInput = {
}, props, {
defaultChecked: undefined,
defaultValue: undefined,
value: value != null ? value : props.defaultValue,
value: value != null ? value : inst._wrapperState.initialValue,
checked: checked != null ? checked : inst._wrapperState.initialChecked,
onChange: inst._wrapperState.onChange,
});
Expand Down Expand Up @@ -138,8 +138,10 @@ var ReactDOMInput = {
warnIfValueIsNull(props);
}

var defaultValue = props.defaultValue;
inst._wrapperState = {
initialChecked: props.defaultChecked || false,
initialValue: props.value != null ? props.value : defaultValue,
listeners: null,
onChange: _handleChange.bind(inst),
};
Expand Down Expand Up @@ -215,6 +217,21 @@ var ReactDOMInput = {
if (newValue !== node.value) {
node.value = newValue;
}
} else {
ReactDOMInput.postUpdateWrapper(inst);
}
},

postUpdateWrapper: function(inst) {
var props = inst._currentElement.props;
if (!props.value && props.defaultValue) {
invariant(inst._rootNodeID, 'Must be mounted to initialize initial input value');
var node = ReactDOMComponentTree.getNodeFromInstance(inst);
if (node.defaultValue != props.defaultValue) {
var tmp = node.value;
node.defaultValue = ''+props.defaultValue;
node.value = tmp;
}
}
},
};
Expand Down
87 changes: 53 additions & 34 deletions src/renderers/dom/client/wrappers/ReactDOMTextarea.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

'use strict';

var DOMPropertyOperations = require('DOMPropertyOperations');
var LinkedValueUtils = require('LinkedValueUtils');
var ReactDOMComponentTree = require('ReactDOMComponentTree');
var ReactUpdates = require('ReactUpdates');
Expand Down Expand Up @@ -66,44 +67,12 @@ var ReactDOMTextarea = {

var value = LinkedValueUtils.getValue(props);

// only bother fetching default value if we're going to use it
if (value == null) {
var defaultValue = props.defaultValue;
// TODO (yungsters): Remove support for children content in <textarea>.
var children = props.children;
if (children != null) {
if (__DEV__) {
warning(
false,
'Use the `defaultValue` or `value` props instead of setting ' +
'children on <textarea>.'
);
}
invariant(
defaultValue == null,
'If you supply `defaultValue` on a <textarea>, do not pass children.'
);
if (Array.isArray(children)) {
invariant(
children.length <= 1,
'<textarea> can only have at most one child.'
);
children = children[0];
}

defaultValue = '' + children;
}
if (defaultValue == null) {
defaultValue = '';
}
}

// The value can be a boolean or object so that's why it's
// forced to be a string.
var nativeProps = Object.assign({}, props, {
defaultValue: '' + (value != null ? value : defaultValue),
value: undefined,
children: undefined,
defaultValue: undefined,
children: '' + (value != null ? value : inst._wrapperState.initialValue),
onChange: inst._wrapperState.onChange,
});

Expand Down Expand Up @@ -142,7 +111,43 @@ var ReactDOMTextarea = {
warnIfValueIsNull(props);
}


var value = LinkedValueUtils.getValue(props);

// only bother fetching default value if we're going to use it
if (value == null) {
var defaultValue = props.defaultValue;
// TODO (yungsters): Remove support for children content in <textarea>.
var children = props.children;
if (children != null) {
if (__DEV__) {
warning(
false,
'Use the `defaultValue` or `value` props instead of setting ' +
'children on <textarea>.'
);
}
invariant(
defaultValue == null,
'If you supply `defaultValue` on a <textarea>, do not pass children.'
);
if (Array.isArray(children)) {
invariant(
children.length <= 1,
'<textarea> can only have at most one child.'
);
children = children[0];
}

defaultValue = '' + children;
}
if (defaultValue == null) {
defaultValue = '';
}
}

inst._wrapperState = {
initialValue: '' + (value != null ? value : defaultValue),
listeners: null,
onChange: _handleChange.bind(inst),
};
Expand All @@ -167,6 +172,20 @@ var ReactDOMTextarea = {
if (newValue !== node.value) {
node.value = newValue;
}
} else {
ReactDOMTextarea.postUpdateWrapper(inst);
}
},

postUpdateWrapper: function(inst) {
var props = inst._currentElement.props;
if (props.value == null && props.defaultValue) {
invariant(inst._rootNodeID, 'Must be mounted to initialize defaultValue');
var node = ReactDOMComponentTree.getNodeFromInstance(inst);
if (node.defaultValue != props.defaultValue) {
node.value = node.value; // Hack to detach value and defaultValue
node.defaultValue = props.defaultValue;
}
}
},
};
Expand Down
13 changes: 11 additions & 2 deletions src/renderers/dom/client/wrappers/__tests__/ReactDOMInput-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ describe('ReactDOMInput', function() {
var EventConstants;
var React;
var ReactDOM;
var ReactDOMServer;
var ReactDOMFeatureFlags;
var ReactLink;
var ReactTestUtils;
Expand All @@ -27,6 +28,7 @@ describe('ReactDOMInput', function() {
EventConstants = require('EventConstants');
React = require('React');
ReactDOM = require('ReactDOM');
ReactDOMServer = require('ReactDOMServer');
ReactDOMFeatureFlags = require('ReactDOMFeatureFlags');
ReactLink = require('ReactLink');
ReactTestUtils = require('ReactTestUtils');
Expand Down Expand Up @@ -67,7 +69,7 @@ describe('ReactDOMInput', function() {

ReactDOM.render(<input type="text" defaultValue="1" />, container);

expect(node.value).toBe('1');
expect(node.value).toBe('0');
});

it('should take `defaultValue` when changing to uncontrolled input', function() {
Expand All @@ -79,7 +81,14 @@ describe('ReactDOMInput', function() {

ReactDOM.render(<input type="text" defaultValue="1" />, container);

expect(node.value).toBe('1');
expect(node.value).toBe('0');
});

it('should render value for SSR', function() {
var image = ReactDOMServer.renderToString(<input type="text" defaultValue="1" />);
var div = document.createElement('div');
div.innerHTML = image;
expect(div.firstChild.getAttribute('value')).toBe('1');
});

it('should display "foobar" for `defaultValue` of `objToString`', function() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ var emptyFunction = require('emptyFunction');
describe('ReactDOMTextarea', function() {
var React;
var ReactDOM;
var ReactDOMServer;
var ReactLink;
var ReactTestUtils;

Expand All @@ -24,6 +25,7 @@ describe('ReactDOMTextarea', function() {
beforeEach(function() {
React = require('React');
ReactDOM = require('ReactDOM');
ReactDOMServer = require('ReactDOMServer');
ReactLink = require('ReactLink');
ReactTestUtils = require('ReactTestUtils');

Expand All @@ -50,7 +52,7 @@ describe('ReactDOMTextarea', function() {

// Changing `defaultValue` should change if no value set.
renderTextarea(<textarea defaultValue="gorilla" />, container, true);
expect(node.value).toEqual('gorilla');
expect(node.value).toEqual('giraffe');

node.value = 'cat';

Expand Down Expand Up @@ -85,6 +87,14 @@ describe('ReactDOMTextarea', function() {
expect(node.value).toBe('foobar');
});

it('should set defaultValue', function() {
var container = document.createElement('div');
ReactDOM.render(<textarea defaultValue="foo" />, container);
ReactDOM.render(<textarea defaultValue="bar" />, container);
ReactDOM.render(<textarea defaultValue="noise" />, container);
expect(container.firstChild.defaultValue).toBe('noise');
});

it('should not render value as an attribute', function() {
var stub = <textarea value="giraffe" onChange={emptyFunction} />;
var node = renderTextarea(stub);
Expand Down Expand Up @@ -113,6 +123,13 @@ describe('ReactDOMTextarea', function() {
expect(node.value).toEqual('gorilla');
});

it('should render value for SSR', function() {
var image = ReactDOMServer.renderToString(<textarea defaultValue="1" />);
var div = document.createElement('div');
div.innerHTML = image;
expect(div.firstChild.innerHTML).toBe('1');
});

it('should allow setting `value` to `true`', function() {
var container = document.createElement('div');
var stub = <textarea value="giraffe" onChange={emptyFunction} />;
Expand Down Expand Up @@ -163,27 +180,27 @@ describe('ReactDOMTextarea', function() {
it('should take updates to `defaultValue` for uncontrolled textarea', function() {
var container = document.createElement('div');

var node = ReactDOM.render(<textarea type="text" defaultValue="0" />, container);
var node = ReactDOM.render(<textarea defaultValue="0" />, container);

expect(node.value).toBe('0');

ReactDOM.render(<textarea type="text" defaultValue="1" />, container);
ReactDOM.render(<textarea defaultValue="1" />, container);

expect(node.value).toBe('1');
expect(node.value).toBe('0');
});

it('should take updates to children in lieu of `defaultValue` for uncontrolled textarea', function() {
var container = document.createElement('div');

var node = ReactDOM.render(<textarea type="text" defaultValue="0" />, container);
var node = ReactDOM.render(<textarea defaultValue="0" />, container);

expect(node.value).toBe('0');

spyOn(console, 'error'); // deprecation warning for `children` content

ReactDOM.render(<textarea type="text">1</textarea>, container);
ReactDOM.render(<textarea>1</textarea>, container);

expect(node.value).toBe('1');
expect(node.value).toBe('0');
});

it('should not incur unnecessary DOM mutations', function() {
Expand Down Expand Up @@ -228,9 +245,9 @@ describe('ReactDOMTextarea', function() {
expect(console.error.argsForCall.length).toBe(1);
expect(node.value).toBe('giraffe');

// Changing children should cause value to change (new behavior of `defaultValue`)
// Changing children should do nothing, it functions like `defaultValue`.
stub = ReactDOM.render(<textarea>gorilla</textarea>, container);
expect(node.value).toEqual('gorilla');
expect(node.value).toEqual('giraffe');
});

it('should not keep value when switching to uncontrolled element if not changed', function() {
Expand All @@ -242,7 +259,7 @@ describe('ReactDOMTextarea', function() {

ReactDOM.render(<textarea defaultValue="gorilla"></textarea>, container);

expect(node.value).toEqual('gorilla');
expect(node.value).toEqual('kitten');
});

it('should keep value when switching to uncontrolled element if changed', function() {
Expand Down
28 changes: 24 additions & 4 deletions src/renderers/dom/shared/ReactDOMComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,14 @@ function postUpdateSelectWrapper() {
ReactDOMSelect.postUpdateWrapper(this);
}

function postUpdateTextareaWrapper() {
ReactDOMTextarea.postUpdateWrapper(this);
}

function postUpdateInputWrapper() {
ReactDOMInput.postUpdateWrapper(this);
}

// For HTML, certain tags should omit their close tag. We keep a whitelist for
// those special-case tags.

Expand Down Expand Up @@ -488,6 +496,7 @@ ReactDOMComponent.Mixin = {
ReactDOMInput.mountWrapper(this, props, nativeParent);
props = ReactDOMInput.getNativeProps(this, props);
transaction.getReactMountReady().enqueue(trapBubbledEventsLocal, this);
transaction.getReactMountReady().enqueue(postUpdateInputWrapper, this);
break;
case 'option':
ReactDOMOption.mountWrapper(this, props, nativeParent);
Expand All @@ -502,6 +511,7 @@ ReactDOMComponent.Mixin = {
ReactDOMTextarea.mountWrapper(this, props, nativeParent);
props = ReactDOMTextarea.getNativeProps(this, props);
transaction.getReactMountReady().enqueue(trapBubbledEventsLocal, this);
transaction.getReactMountReady().enqueue(postUpdateTextareaWrapper, this);
break;
}

Expand Down Expand Up @@ -811,10 +821,20 @@ ReactDOMComponent.Mixin = {
context
);

if (this._tag === 'select') {
// <select> value update needs to occur after <option> children
// reconciliation
transaction.getReactMountReady().enqueue(postUpdateSelectWrapper, this);
switch (this._tag) {
case 'button':
break;
case 'input':
transaction.getReactMountReady().enqueue(postUpdateInputWrapper, this);
break;
case 'option':
break;
case 'select':
transaction.getReactMountReady().enqueue(postUpdateSelectWrapper, this);
break;
case 'textarea':
transaction.getReactMountReady().enqueue(postUpdateTextareaWrapper, this);
break;
}
},

Expand Down

0 comments on commit fef7028

Please sign in to comment.