Skip to content

Commit d29640c

Browse files
committed
Make Typeahead a controlled component. Fixes #166
1 parent 65f07af commit d29640c

File tree

3 files changed

+44
-23
lines changed

3 files changed

+44
-23
lines changed

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@
3333
},
3434
"dependencies": {
3535
"classnames": "^1.2.0",
36-
"fuzzy": "^0.1.0"
36+
"fuzzy": "^0.1.0",
37+
"react-addons-update": "^0.14.7"
3738
},
3839
"peerDependencies": {
3940
"react": ">= 0.14.0"

src/tokenizer/index.js

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ var Token = require('./token');
77
var KeyEvent = require('../keyevent');
88
var Typeahead = require('../typeahead');
99
var classNames = require('classnames');
10+
var update = require('react-addons-update');
1011

1112
function _arraysAreDifferent(array1, array2) {
1213
if (array1.length != array2.length){
@@ -56,7 +57,8 @@ var TypeaheadTokenizer = React.createClass({
5657
return {
5758
// We need to copy this to avoid incorrect sharing
5859
// of state across instances (e.g., via getDefaultProps())
59-
selected: this.props.defaultSelected.slice(0)
60+
selected: this.props.defaultSelected.slice(0),
61+
value: this.props.defaultValue
6062
};
6163
},
6264

@@ -84,7 +86,9 @@ var TypeaheadTokenizer = React.createClass({
8486
componentWillReceiveProps: function(nextProps){
8587
// if we get new defaultProps, update selected
8688
if (_arraysAreDifferent(this.props.defaultSelected, nextProps.defaultSelected)){
87-
this.setState({selected: nextProps.defaultSelected.slice(0)})
89+
this.setState({
90+
selected: nextProps.defaultSelected.slice(0)
91+
});
8892
}
8993
},
9094

@@ -140,8 +144,7 @@ var TypeaheadTokenizer = React.createClass({
140144
var entry = this.refs.typeahead.refs.entry;
141145
if (entry.selectionStart == entry.selectionEnd &&
142146
entry.selectionStart == 0) {
143-
this._removeTokenForValue(
144-
this.state.selected[this.state.selected.length - 1]);
147+
this._removeTokenForValue(this.state.selected[this.state.selected.length - 1]);
145148
event.preventDefault();
146149
}
147150
},
@@ -152,22 +155,30 @@ var TypeaheadTokenizer = React.createClass({
152155
return;
153156
}
154157

155-
this.state.selected.splice(index, 1);
156-
this.setState({selected: this.state.selected});
158+
this.setState({
159+
selected: update(this.state.selected, {$splice: [[index, 1]]})
160+
});
157161
this.props.onTokenRemove(value);
158162
return;
159163
},
160164

161165
_addTokenForValue: function(value) {
162-
if (this.state.selected.indexOf(value) != -1) {
166+
if (this.state.selected.indexOf(value) !== -1) {
163167
return;
164168
}
165-
this.state.selected.push(value);
166-
this.setState({selected: this.state.selected});
167-
this.refs.typeahead.setEntryText("");
169+
this.setState({
170+
selected: update(this.state.selected, {$push: [value]}),
171+
value: ""
172+
});
168173
this.props.onTokenAdd(value);
169174
},
170175

176+
_onChange: function(event) {
177+
this.setState({
178+
value: event.target.value
179+
});
180+
},
181+
171182
render: function() {
172183
var classes = {};
173184
classes[this.props.customClasses.typeahead] = !!this.props.customClasses.typeahead;
@@ -188,14 +199,16 @@ var TypeaheadTokenizer = React.createClass({
188199
options={this._getOptionsForTypeahead()}
189200
defaultValue={this.props.defaultValue}
190201
maxVisible={this.props.maxVisible}
202+
onChange={this._onChange}
191203
onOptionSelected={this._addTokenForValue}
192204
onKeyDown={this._onKeyDown}
193205
onKeyUp={this.props.onKeyUp}
194206
onFocus={this.props.onFocus}
195207
onBlur={this.props.onBlur}
196208
displayOption={this.props.displayOption}
197209
defaultClassNames={this.props.defaultClassNames}
198-
filterOption={this.props.filterOption} />
210+
filterOption={this.props.filterOption}
211+
value={this.state.value} />
199212
</div>
200213
);
201214
}

src/typeahead/index.js

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ var Typeahead = React.createClass({
124124
_hasCustomValue: function() {
125125
if (this.props.allowCustomValues > 0 &&
126126
this.state.entryValue.length >= this.props.allowCustomValues &&
127-
this.state.visible.indexOf(this.state.entryValue) < 0) {
127+
this.state.visible.indexOf(this.state.entryValue) === -1) {
128128
return true;
129129
}
130130
return false;
@@ -183,17 +183,21 @@ var Typeahead = React.createClass({
183183
var formInputOptionString = formInputOption(option);
184184

185185
nEntry.value = optionString;
186-
this.setState({visible: this.getOptionsForValue(optionString, this.props.options),
187-
selection: formInputOptionString,
188-
entryValue: optionString});
186+
this.setState({
187+
visible: this.getOptionsForValue(optionString, this.props.options),
188+
selection: formInputOptionString,
189+
entryValue: optionString
190+
});
189191
return this.props.onOptionSelected(option, event);
190192
},
191193

192194
_onTextEntryUpdated: function() {
193195
var value = this.refs.entry.value;
194-
this.setState({visible: this.getOptionsForValue(value, this.props.options),
195-
selection: null,
196-
entryValue: value});
196+
this.setState({
197+
visible: this.getOptionsForValue(value, this.props.options),
198+
selection: null,
199+
entryValue: value
200+
});
197201
},
198202

199203
_onEnter: function(event) {
@@ -215,7 +219,7 @@ var Typeahead = React.createClass({
215219
var option = selection ?
216220
selection : (this.state.visible.length > 0 ? this.state.visible[0] : null);
217221

218-
if (option === null && this._hasCustomValue()) {
222+
if (option === null) {
219223
option = this._getCustomValue();
220224
}
221225

@@ -252,7 +256,9 @@ var Typeahead = React.createClass({
252256
newIndex -= length;
253257
}
254258

255-
this.setState({selectionIndex: newIndex});
259+
this.setState({
260+
selectionIndex: newIndex
261+
});
256262
},
257263

258264
navDown: function() {
@@ -292,7 +298,8 @@ var Typeahead = React.createClass({
292298

293299
componentWillReceiveProps: function(nextProps) {
294300
this.setState({
295-
visible: this.getOptionsForValue(nextProps.entryValue, nextProps.options)
301+
visible: this.getOptionsForValue(nextProps.value, nextProps.options),
302+
entryValue: nextProps.value
296303
});
297304
},
298305

@@ -316,13 +323,13 @@ var Typeahead = React.createClass({
316323
{...this.props.inputProps}
317324
placeholder={this.props.placeholder}
318325
className={inputClassList}
319-
value={this.state.entryValue}
320326
defaultValue={this.props.defaultValue}
321327
onChange={this._onChange}
322328
onKeyDown={this._onKeyDown}
323329
onKeyUp={this.props.onKeyUp}
324330
onFocus={this.props.onFocus}
325331
onBlur={this.props.onBlur}
332+
value={this.props.value}
326333
/>
327334
{ this._renderIncrementalSearchResults() }
328335
</div>

0 commit comments

Comments
 (0)