Skip to content

Commit 5242a21

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

File tree

3 files changed

+58
-25
lines changed

3 files changed

+58
-25
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: 27 additions & 10 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

@@ -140,8 +142,7 @@ var TypeaheadTokenizer = React.createClass({
140142
var entry = this.refs.typeahead.refs.entry;
141143
if (entry.selectionStart == entry.selectionEnd &&
142144
entry.selectionStart == 0) {
143-
this._removeTokenForValue(
144-
this.state.selected[this.state.selected.length - 1]);
145+
this._removeTokenForValue(this.state.selected[this.state.selected.length - 1]);
145146
event.preventDefault();
146147
}
147148
},
@@ -152,22 +153,36 @@ var TypeaheadTokenizer = React.createClass({
152153
return;
153154
}
154155

155-
this.state.selected.splice(index, 1);
156-
this.setState({selected: this.state.selected});
156+
this.setState(
157+
{
158+
selected: update(this.state.selected, {$splice: [[index, 1]]})
159+
}
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+
{
171+
selected: update(this.state.selected, {$push: [value]}),
172+
value: ""
173+
}
174+
);
168175
this.props.onTokenAdd(value);
169176
},
170177

178+
_onChange: function(event) {
179+
this.setState(
180+
{
181+
value: event.target.value
182+
}
183+
);
184+
},
185+
171186
render: function() {
172187
var classes = {};
173188
classes[this.props.customClasses.typeahead] = !!this.props.customClasses.typeahead;
@@ -188,14 +203,16 @@ var TypeaheadTokenizer = React.createClass({
188203
options={this._getOptionsForTypeahead()}
189204
defaultValue={this.props.defaultValue}
190205
maxVisible={this.props.maxVisible}
206+
onChange={this._onChange}
191207
onOptionSelected={this._addTokenForValue}
192208
onKeyDown={this._onKeyDown}
193209
onKeyUp={this.props.onKeyUp}
194210
onFocus={this.props.onFocus}
195211
onBlur={this.props.onBlur}
196212
displayOption={this.props.displayOption}
197213
defaultClassNames={this.props.defaultClassNames}
198-
filterOption={this.props.filterOption} />
214+
filterOption={this.props.filterOption}
215+
value={this.state.value} />
199216
</div>
200217
);
201218
}

src/typeahead/index.js

Lines changed: 29 additions & 14 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,25 @@ 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+
{
188+
visible: this.getOptionsForValue(optionString, this.props.options),
189+
selection: formInputOptionString,
190+
entryValue: optionString
191+
}
192+
);
189193
return this.props.onOptionSelected(option, event);
190194
},
191195

192196
_onTextEntryUpdated: function() {
193197
var value = this.refs.entry.value;
194-
this.setState({visible: this.getOptionsForValue(value, this.props.options),
195-
selection: null,
196-
entryValue: value});
198+
this.setState(
199+
{
200+
visible: this.getOptionsForValue(value, this.props.options),
201+
selection: null,
202+
entryValue: value
203+
}
204+
);
197205
},
198206

199207
_onEnter: function(event) {
@@ -205,17 +213,19 @@ var Typeahead = React.createClass({
205213
},
206214

207215
_onEscape: function() {
208-
this.setState({
209-
selectionIndex: null
210-
});
216+
this.setState(
217+
{
218+
selectionIndex: null
219+
}
220+
);
211221
},
212222

213223
_onTab: function(event) {
214224
var selection = this.getSelection();
215225
var option = selection ?
216226
selection : (this.state.visible.length > 0 ? this.state.visible[0] : null);
217227

218-
if (option === null && this._hasCustomValue()) {
228+
if (option === null) {
219229
option = this._getCustomValue();
220230
}
221231

@@ -252,7 +262,11 @@ var Typeahead = React.createClass({
252262
newIndex -= length;
253263
}
254264

255-
this.setState({selectionIndex: newIndex});
265+
this.setState(
266+
{
267+
selectionIndex: newIndex
268+
}
269+
);
256270
},
257271

258272
navDown: function() {
@@ -292,7 +306,8 @@ var Typeahead = React.createClass({
292306

293307
componentWillReceiveProps: function(nextProps) {
294308
this.setState({
295-
visible: this.getOptionsForValue(nextProps.entryValue, nextProps.options)
309+
visible: this.getOptionsForValue(nextProps.value, nextProps.options),
310+
entryValue: nextProps.value
296311
});
297312
},
298313

@@ -316,13 +331,13 @@ var Typeahead = React.createClass({
316331
{...this.props.inputProps}
317332
placeholder={this.props.placeholder}
318333
className={inputClassList}
319-
value={this.state.entryValue}
320334
defaultValue={this.props.defaultValue}
321335
onChange={this._onChange}
322336
onKeyDown={this._onKeyDown}
323337
onKeyUp={this.props.onKeyUp}
324338
onFocus={this.props.onFocus}
325339
onBlur={this.props.onBlur}
340+
value={this.props.value}
326341
/>
327342
{ this._renderIncrementalSearchResults() }
328343
</div>

0 commit comments

Comments
 (0)