Dynamic Input Component

Summary:
This is a proposal based loosely on the discussions I've had with @paulshen and @jwalke. It implements a shim for `React.DOM.input` (used as `<input>`) that supports two different use cases depending on whether `value` is provided or not.

If a `value` is //not// provided, the input will be initialized with the empty string (or `defaultValue`) and anytime the user changes the input, the `onChange` (or `onTextChange`) handler will be fired and the DOM will reflect the new changes.

  React.renderComponent(
    <input type="text" defaultValue="Untitled" onTextChange={handleChange} />,
    container
  );

If a `value` is provided, the input will be initialized to that value. Anytime the user changes the input, the `onChange` (or `onTextChange`) handler will be fired. However, the DOM will //not// reflect the new changes. If a `value` is provided, it is the responsibility of the owner to update the `value` prop passed in.

  var value = "Untitled";
  var input = React.renderComponent(
    <input type="text" value={value} onTextChange={handleChange} />,
    container
  );
  function handleChange(event) {
    // Do something cool like strip out non-numbers.
    var value = event.target.value.replace(/\D/g, '');
    input.setProps({value: value});
  }

This is just a start and we should build similar components for `textarea` and `select`. Also, this does not inject the new components because the changes are not backward compatible. Once we change all `<input>` uses to use `ReactDOMInput`, then we can inject.
This commit is contained in:
CommitSyncScript
2013-06-24 16:13:14 -07:00
committed by Paul O’Shannessy
parent 7c60bb3e54
commit 4f2d8dfe72
3 changed files with 129 additions and 5 deletions
+6 -5
View File
@@ -45,14 +45,15 @@ function inject() {
'TextChangeEventPlugin': TextChangeEventPlugin
});
/*
* This is a bit of a hack. We need to override the <form> element
* to be a composite component because IE8 does not bubble or capture
* submit to the top level. In order to make this work with our
* dependency graph we need to inject it here.
/**
* This is a bit of a hack. We need to override the <form> element to be a
* composite component because IE8 does not bubble or capture submit to the
* top level. In order to make this work with our dependency graph we need to
* inject it here.
*/
ReactDOM.injection.injectComponentClasses({
form: ReactDOMForm
// TODO: Inject `ReactDOMInput`.
});
}
+123
View File
@@ -0,0 +1,123 @@
/**
* Copyright 2013 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @providesModule ReactDOMInput
*/
"use strict";
var DOMPropertyOperations = require('DOMPropertyOperations');
var ReactCompositeComponent = require('ReactCompositeComponent');
var ReactDOM = require('ReactDOM');
var merge = require('merge');
// Store a reference to the <input> `ReactNativeComponent`.
var input = ReactDOM.input;
/**
* Implements an <input> native component that allows setting these optional
* props: `checked`, `value`, `defaultChecked`, and `defaultValue`.
*
* If `checked` or `value` are not supplied (or null/undefined), user actions
* that affect the checked state or value will trigger updates to the element.
*
* If they are supplied (and not null/undefined), the rendered element will not
* trigger updates to the element. Instead, the props must change in order for
* the rendered element to be updated.
*
* The rendered element will be initialized as unchecked (or `defaultChecked`)
* with an empty value (or `defaultValue`).
*
* @see http://www.w3.org/TR/2012/WD-html5-20121025/the-input-element.html
*/
var ReactDOMInput = ReactCompositeComponent.createClass({
getInitialState: function() {
return {
checked: this.props.defaultChecked || false,
value: this.props.defaultValue || ''
};
},
getChecked: function() {
return this.props.checked != null ? this.props.checked : this.state.checked;
},
getValue: function() {
return this.props.value != null ? this.props.value : this.state.value;
},
render: function() {
// Clone `this.props` so we don't mutate the input.
var props = merge(this.props);
props.checked = this.getChecked();
props.value = this.getValue();
// TODO: Re-implement `onChange` with the implementation of `onTextChange`.
if (props.type === 'text' || props.type === 'password') {
props.onTextChange = this.handleTextChange;
} else {
props.onChange = this.handleChange;
}
return input(props, this.props.children);
},
componentDidUpdate: function(prevProps, prevState, rootNode) {
if (this.props.checked != null) {
DOMPropertyOperations.setValueForProperty(
rootNode,
'checked',
this.props.checked || false
);
}
if (this.props.value != null) {
DOMPropertyOperations.setValueForProperty(
rootNode,
'value',
this.props.value || ''
);
}
},
handleTextChange: ReactCompositeComponent.autoBind(function(event) {
var returnValue;
if (this.props.onTextChange) {
returnValue = this.props.onTextChange(event);
}
this.setState({
checked: event.target.checked,
value: event.target.value
});
return returnValue;
}),
handleChange: ReactCompositeComponent.autoBind(function(event) {
var returnValue;
if (this.props.onChange) {
returnValue = this.props.onChange(event);
}
this.setState({
checked: event.target.checked,
value: event.target.value
});
return returnValue;
})
});
module.exports = ReactDOMInput;