From ea089fdfe62b3193740ee4a30acb6827a905a02c Mon Sep 17 00:00:00 2001 From: Ben Alpert Date: Sun, 5 Apr 2015 12:25:27 -0700 Subject: [PATCH] Fix server-side rendering of updates value + // manually; we need the initial state only for server rendering + if (this.state.selected != null) { + props = assign({}, props, {selected: this.state.selected}); + } + + return option(props, this.props.children); } }); diff --git a/src/browser/ui/dom/components/ReactDOMSelect.js b/src/browser/ui/dom/components/ReactDOMSelect.js index 9ad78ba868..1573d64a2b 100644 --- a/src/browser/ui/dom/components/ReactDOMSelect.js +++ b/src/browser/ui/dom/components/ReactDOMSelect.js @@ -17,17 +17,21 @@ var ReactBrowserComponentMixin = require('ReactBrowserComponentMixin'); var ReactClass = require('ReactClass'); var ReactElement = require('ReactElement'); var ReactUpdates = require('ReactUpdates'); +var ReactPropTypes = require('ReactPropTypes'); var assign = require('Object.assign'); var findDOMNode = require('findDOMNode'); var select = ReactElement.createFactory('select'); +var valueContextKey = + '__ReactDOMSelect_value$' + Math.random().toString(36).slice(2); + function updateOptionsIfPendingUpdateAndMounted() { /*jshint validthis:true */ if (this._pendingUpdate) { this._pendingUpdate = false; - var value = LinkedValueUtils.getValue(this); + var value = LinkedValueUtils.getValue(this.props); if (value != null && this.isMounted()) { updateOptions(this, value); } @@ -116,11 +120,38 @@ var ReactDOMSelect = ReactClass.createClass({ mixins: [AutoFocusMixin, LinkedValueUtils.Mixin, ReactBrowserComponentMixin], + statics: { + valueContextKey: valueContextKey + }, + propTypes: { defaultValue: selectValueType, value: selectValueType }, + getInitialState: function() { + // Pass down initial value so initial generated markup has correct + // `selected` attributes + var value = LinkedValueUtils.getValue(this.props); + if (value != null) { + return {initialValue: value}; + } else { + return {initialValue: this.props.defaultValue}; + } + }, + + childContextTypes: (function() { + var obj = {}; + obj[valueContextKey] = ReactPropTypes.any; + return obj; + })(), + + getChildContext: function() { + var obj = {}; + obj[valueContextKey] = this.state.initialValue; + return obj; + }, + render: function() { // Clone `this.props` so we don't mutate the input. var props = assign({}, this.props); @@ -135,17 +166,14 @@ var ReactDOMSelect = ReactClass.createClass({ this._pendingUpdate = false; }, - componentDidMount: function() { - var value = LinkedValueUtils.getValue(this); - if (value != null) { - updateOptions(this, value); - } else if (this.props.defaultValue != null) { - updateOptions(this, this.props.defaultValue); - } + componentWillReceiveProps: function(nextProps) { + // After the initial mount, we control selected-ness manually so don't pass + // the context value down + this.setState({initialValue: null}); }, componentDidUpdate: function(prevProps) { - var value = LinkedValueUtils.getValue(this); + var value = LinkedValueUtils.getValue(this.props); if (value != null) { this._pendingUpdate = false; updateOptions(this, value); @@ -162,7 +190,7 @@ var ReactDOMSelect = ReactClass.createClass({ _handleChange: function(event) { var returnValue; - var onChange = LinkedValueUtils.getOnChange(this); + var onChange = LinkedValueUtils.getOnChange(this.props); if (onChange) { returnValue = onChange.call(this, event); } diff --git a/src/browser/ui/dom/components/ReactDOMTextarea.js b/src/browser/ui/dom/components/ReactDOMTextarea.js index af4a460eff..0261005c40 100644 --- a/src/browser/ui/dom/components/ReactDOMTextarea.js +++ b/src/browser/ui/dom/components/ReactDOMTextarea.js @@ -84,7 +84,7 @@ var ReactDOMTextarea = ReactClass.createClass({ if (defaultValue == null) { defaultValue = ''; } - var value = LinkedValueUtils.getValue(this); + var value = LinkedValueUtils.getValue(this.props); return { // We save the initial value so that `ReactDOMComponent` doesn't update // `textContent` (unnecessary since we update value). @@ -113,7 +113,7 @@ var ReactDOMTextarea = ReactClass.createClass({ }, componentDidUpdate: function(prevProps, prevState, prevContext) { - var value = LinkedValueUtils.getValue(this); + var value = LinkedValueUtils.getValue(this.props); if (value != null) { var rootNode = findDOMNode(this); // Cast `value` to a string to ensure the value is set correctly. While @@ -124,7 +124,7 @@ var ReactDOMTextarea = ReactClass.createClass({ _handleChange: function(event) { var returnValue; - var onChange = LinkedValueUtils.getOnChange(this); + var onChange = LinkedValueUtils.getOnChange(this.props); if (onChange) { returnValue = onChange.call(this, event); } diff --git a/src/browser/ui/dom/components/__tests__/ReactDOMSelect-test.js b/src/browser/ui/dom/components/__tests__/ReactDOMSelect-test.js index 1b369ec627..3166d913a5 100644 --- a/src/browser/ui/dom/components/__tests__/ReactDOMSelect-test.js +++ b/src/browser/ui/dom/components/__tests__/ReactDOMSelect-test.js @@ -315,4 +315,85 @@ describe('ReactDOMSelect', function() { expect(link.requestChange.mock.calls[0][0]).toEqual('gorilla'); }); + + it('should support server-side rendering', function() { + var stub = + ; + var markup = React.renderToString(stub); + expect(markup).toContain(' + + + ; + var markup = React.renderToString(stub); + expect(markup).toContain(' + + + ; + var markup = React.renderToString(stub); + expect(markup).toContain(' + + + , + container + ); + var node = React.findDOMNode(select); + + expect(node.options[0].selected).toBe(false); // monkey + expect(node.options[1].selected).toBe(true); // giraffe + expect(node.options[2].selected).toBe(false); // gorilla + + React.render( + , + container + ); + + expect(node.options[0].selected).toBe(false); // monkey + expect(node.options[1].selected).toBe(false); // gorilla + + React.render( + , + container + ); + + expect(node.options[0].selected).toBe(false); // monkey + expect(node.options[1].selected).toBe(false); // giraffe + expect(node.options[2].selected).toBe(false); // gorilla + }); });