Skip to content

Commit 4cafd97

Browse files
nosillegJason Ellison
authored andcommitted
Make Typeahead a controlled component. Fixes #166
1 parent 7b1617d commit 4cafd97

3 files changed

Lines changed: 44 additions & 23 deletions

File tree

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){
@@ -58,7 +59,8 @@ var TypeaheadTokenizer = React.createClass({
5859
return {
5960
// We need to copy this to avoid incorrect sharing
6061
// of state across instances (e.g., via getDefaultProps())
61-
selected: this.props.defaultSelected.slice(0)
62+
selected: this.props.defaultSelected.slice(0),
63+
value: this.props.defaultValue
6264
};
6365
},
6466

@@ -88,7 +90,9 @@ var TypeaheadTokenizer = React.createClass({
8890
componentWillReceiveProps: function(nextProps){
8991
// if we get new defaultProps, update selected
9092
if (_arraysAreDifferent(this.props.defaultSelected, nextProps.defaultSelected)){
91-
this.setState({selected: nextProps.defaultSelected.slice(0)})
93+
this.setState({
94+
selected: nextProps.defaultSelected.slice(0)
95+
});
9296
}
9397
},
9498

@@ -144,8 +148,7 @@ var TypeaheadTokenizer = React.createClass({
144148
var entry = this.refs.typeahead.refs.entry;
145149
if (entry.selectionStart == entry.selectionEnd &&
146150
entry.selectionStart == 0) {
147-
this._removeTokenForValue(
148-
this.state.selected[this.state.selected.length - 1]);
151+
this._removeTokenForValue(this.state.selected[this.state.selected.length - 1]);
149152
event.preventDefault();
150153
}
151154
},
@@ -156,22 +159,30 @@ var TypeaheadTokenizer = React.createClass({
156159
return;
157160
}
158161

159-
this.state.selected.splice(index, 1);
160-
this.setState({selected: this.state.selected});
162+
this.setState({
163+
selected: update(this.state.selected, {$splice: [[index, 1]]})
164+
});
161165
this.props.onTokenRemove(value);
162166
return;
163167
},
164168

165169
_addTokenForValue: function(value) {
166-
if (this.state.selected.indexOf(value) != -1) {
170+
if (this.state.selected.indexOf(value) !== -1) {
167171
return;
168172
}
169-
this.state.selected.push(value);
170-
this.setState({selected: this.state.selected});
171-
this.refs.typeahead.setEntryText("");
173+
this.setState({
174+
selected: update(this.state.selected, {$push: [value]}),
175+
value: ""
176+
});
172177
this.props.onTokenAdd(value);
173178
},
174179

180+
_onChange: function(event) {
181+
this.setState({
182+
value: event.target.value
183+
});
184+
},
185+
175186
render: function() {
176187
var classes = {};
177188
classes[this.props.customClasses.typeahead] = !!this.props.customClasses.typeahead;
@@ -193,6 +204,7 @@ var TypeaheadTokenizer = React.createClass({
193204
options={this._getOptionsForTypeahead()}
194205
defaultValue={this.props.defaultValue}
195206
maxVisible={this.props.maxVisible}
207+
onChange={this._onChange}
196208
onOptionSelected={this._addTokenForValue}
197209
onKeyDown={this._onKeyDown}
198210
onKeyPress={this.props.onKeyPress}
@@ -201,7 +213,8 @@ var TypeaheadTokenizer = React.createClass({
201213
onBlur={this.props.onBlur}
202214
displayOption={this.props.displayOption}
203215
defaultClassNames={this.props.defaultClassNames}
204-
filterOption={this.props.filterOption} />
216+
filterOption={this.props.filterOption}
217+
value={this.state.value} />
205218
</div>
206219
);
207220
}

src/typeahead/index.js

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ var Typeahead = React.createClass({
128128
_hasCustomValue: function() {
129129
if (this.props.allowCustomValues > 0 &&
130130
this.state.entryValue.length >= this.props.allowCustomValues &&
131-
this.state.visible.indexOf(this.state.entryValue) < 0) {
131+
this.state.visible.indexOf(this.state.entryValue) === -1) {
132132
return true;
133133
}
134134
return false;
@@ -188,17 +188,21 @@ var Typeahead = React.createClass({
188188
var formInputOptionString = formInputOption(option);
189189

190190
nEntry.value = optionString;
191-
this.setState({visible: this.getOptionsForValue(optionString, this.props.options),
192-
selection: formInputOptionString,
193-
entryValue: optionString});
191+
this.setState({
192+
visible: this.getOptionsForValue(optionString, this.props.options),
193+
selection: formInputOptionString,
194+
entryValue: optionString
195+
});
194196
return this.props.onOptionSelected(option, event);
195197
},
196198

197199
_onTextEntryUpdated: function() {
198200
var value = this.refs.entry.value;
199-
this.setState({visible: this.getOptionsForValue(value, this.props.options),
200-
selection: null,
201-
entryValue: value});
201+
this.setState({
202+
visible: this.getOptionsForValue(value, this.props.options),
203+
selection: null,
204+
entryValue: value
205+
});
202206
},
203207

204208
_onEnter: function(event) {
@@ -220,7 +224,7 @@ var Typeahead = React.createClass({
220224
var option = selection ?
221225
selection : (this.state.visible.length > 0 ? this.state.visible[0] : null);
222226

223-
if (option === null && this._hasCustomValue()) {
227+
if (option === null) {
224228
option = this._getCustomValue();
225229
}
226230

@@ -257,7 +261,9 @@ var Typeahead = React.createClass({
257261
newIndex -= length;
258262
}
259263

260-
this.setState({selectionIndex: newIndex});
264+
this.setState({
265+
selectionIndex: newIndex
266+
});
261267
},
262268

263269
navDown: function() {
@@ -297,7 +303,8 @@ var Typeahead = React.createClass({
297303

298304
componentWillReceiveProps: function(nextProps) {
299305
this.setState({
300-
visible: this.getOptionsForValue(this.state.entryValue, nextProps.options)
306+
visible: this.getOptionsForValue(nextProps.value, nextProps.options),
307+
entryValue: nextProps.value
301308
});
302309
},
303310

@@ -322,14 +329,14 @@ var Typeahead = React.createClass({
322329
placeholder={this.props.placeholder}
323330
disabled={this.props.disabled}
324331
className={inputClassList}
325-
value={this.state.entryValue}
326332
defaultValue={this.props.defaultValue}
327333
onChange={this._onChange}
328334
onKeyDown={this._onKeyDown}
329335
onKeyPress={this.props.onKeyPress}
330336
onKeyUp={this.props.onKeyUp}
331337
onFocus={this.props.onFocus}
332338
onBlur={this.props.onBlur}
339+
value={this.props.value}
333340
/>
334341
{ this._renderIncrementalSearchResults() }
335342
</div>

0 commit comments

Comments
 (0)