From 4cafd97d5d70a0986041cf9ae35423086d63ad8b Mon Sep 17 00:00:00 2001 From: Jason Ellison Date: Tue, 22 Mar 2016 12:10:00 +0000 Subject: [PATCH] Make Typeahead a controlled component. Fixes #166 --- package.json | 3 ++- src/tokenizer/index.js | 35 ++++++++++++++++++++++++----------- src/typeahead/index.js | 29 ++++++++++++++++++----------- 3 files changed, 44 insertions(+), 23 deletions(-) diff --git a/package.json b/package.json index 050916b1..47ca8139 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,8 @@ }, "dependencies": { "classnames": "^1.2.0", - "fuzzy": "^0.1.0" + "fuzzy": "^0.1.0", + "react-addons-update": "^0.14.7" }, "peerDependencies": { "react": ">= 0.14.0" diff --git a/src/tokenizer/index.js b/src/tokenizer/index.js index 9cb05028..86fdd8f3 100644 --- a/src/tokenizer/index.js +++ b/src/tokenizer/index.js @@ -7,6 +7,7 @@ var Token = require('./token'); var KeyEvent = require('../keyevent'); var Typeahead = require('../typeahead'); var classNames = require('classnames'); +var update = require('react-addons-update'); function _arraysAreDifferent(array1, array2) { if (array1.length != array2.length){ @@ -58,7 +59,8 @@ var TypeaheadTokenizer = React.createClass({ return { // We need to copy this to avoid incorrect sharing // of state across instances (e.g., via getDefaultProps()) - selected: this.props.defaultSelected.slice(0) + selected: this.props.defaultSelected.slice(0), + value: this.props.defaultValue }; }, @@ -88,7 +90,9 @@ var TypeaheadTokenizer = React.createClass({ componentWillReceiveProps: function(nextProps){ // if we get new defaultProps, update selected if (_arraysAreDifferent(this.props.defaultSelected, nextProps.defaultSelected)){ - this.setState({selected: nextProps.defaultSelected.slice(0)}) + this.setState({ + selected: nextProps.defaultSelected.slice(0) + }); } }, @@ -144,8 +148,7 @@ var TypeaheadTokenizer = React.createClass({ var entry = this.refs.typeahead.refs.entry; if (entry.selectionStart == entry.selectionEnd && entry.selectionStart == 0) { - this._removeTokenForValue( - this.state.selected[this.state.selected.length - 1]); + this._removeTokenForValue(this.state.selected[this.state.selected.length - 1]); event.preventDefault(); } }, @@ -156,22 +159,30 @@ var TypeaheadTokenizer = React.createClass({ return; } - this.state.selected.splice(index, 1); - this.setState({selected: this.state.selected}); + this.setState({ + selected: update(this.state.selected, {$splice: [[index, 1]]}) + }); this.props.onTokenRemove(value); return; }, _addTokenForValue: function(value) { - if (this.state.selected.indexOf(value) != -1) { + if (this.state.selected.indexOf(value) !== -1) { return; } - this.state.selected.push(value); - this.setState({selected: this.state.selected}); - this.refs.typeahead.setEntryText(""); + this.setState({ + selected: update(this.state.selected, {$push: [value]}), + value: "" + }); this.props.onTokenAdd(value); }, + _onChange: function(event) { + this.setState({ + value: event.target.value + }); + }, + render: function() { var classes = {}; classes[this.props.customClasses.typeahead] = !!this.props.customClasses.typeahead; @@ -193,6 +204,7 @@ var TypeaheadTokenizer = React.createClass({ options={this._getOptionsForTypeahead()} defaultValue={this.props.defaultValue} maxVisible={this.props.maxVisible} + onChange={this._onChange} onOptionSelected={this._addTokenForValue} onKeyDown={this._onKeyDown} onKeyPress={this.props.onKeyPress} @@ -201,7 +213,8 @@ var TypeaheadTokenizer = React.createClass({ onBlur={this.props.onBlur} displayOption={this.props.displayOption} defaultClassNames={this.props.defaultClassNames} - filterOption={this.props.filterOption} /> + filterOption={this.props.filterOption} + value={this.state.value} /> ); } diff --git a/src/typeahead/index.js b/src/typeahead/index.js index 797136e6..7212ea25 100644 --- a/src/typeahead/index.js +++ b/src/typeahead/index.js @@ -128,7 +128,7 @@ var Typeahead = React.createClass({ _hasCustomValue: function() { if (this.props.allowCustomValues > 0 && this.state.entryValue.length >= this.props.allowCustomValues && - this.state.visible.indexOf(this.state.entryValue) < 0) { + this.state.visible.indexOf(this.state.entryValue) === -1) { return true; } return false; @@ -188,17 +188,21 @@ var Typeahead = React.createClass({ var formInputOptionString = formInputOption(option); nEntry.value = optionString; - this.setState({visible: this.getOptionsForValue(optionString, this.props.options), - selection: formInputOptionString, - entryValue: optionString}); + this.setState({ + visible: this.getOptionsForValue(optionString, this.props.options), + selection: formInputOptionString, + entryValue: optionString + }); return this.props.onOptionSelected(option, event); }, _onTextEntryUpdated: function() { var value = this.refs.entry.value; - this.setState({visible: this.getOptionsForValue(value, this.props.options), - selection: null, - entryValue: value}); + this.setState({ + visible: this.getOptionsForValue(value, this.props.options), + selection: null, + entryValue: value + }); }, _onEnter: function(event) { @@ -220,7 +224,7 @@ var Typeahead = React.createClass({ var option = selection ? selection : (this.state.visible.length > 0 ? this.state.visible[0] : null); - if (option === null && this._hasCustomValue()) { + if (option === null) { option = this._getCustomValue(); } @@ -257,7 +261,9 @@ var Typeahead = React.createClass({ newIndex -= length; } - this.setState({selectionIndex: newIndex}); + this.setState({ + selectionIndex: newIndex + }); }, navDown: function() { @@ -297,7 +303,8 @@ var Typeahead = React.createClass({ componentWillReceiveProps: function(nextProps) { this.setState({ - visible: this.getOptionsForValue(this.state.entryValue, nextProps.options) + visible: this.getOptionsForValue(nextProps.value, nextProps.options), + entryValue: nextProps.value }); }, @@ -322,7 +329,6 @@ var Typeahead = React.createClass({ placeholder={this.props.placeholder} disabled={this.props.disabled} className={inputClassList} - value={this.state.entryValue} defaultValue={this.props.defaultValue} onChange={this._onChange} onKeyDown={this._onKeyDown} @@ -330,6 +336,7 @@ var Typeahead = React.createClass({ onKeyUp={this.props.onKeyUp} onFocus={this.props.onFocus} onBlur={this.props.onBlur} + value={this.props.value} /> { this._renderIncrementalSearchResults() }