Skip to content
This repository was archived by the owner on Jul 19, 2019. It is now read-only.

Stop keeping value in state #65

Merged
merged 4 commits into from
Mar 31, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions examples/async-data/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ let App = React.createClass({

getInitialState () {
return {
value: '',
unitedStates: getStates(),
loading: false
}
Expand All @@ -27,16 +28,17 @@ let App = React.createClass({
labelText="Choose a state from the US"
inputProps={{name: "US state"}}
ref="autocomplete"
value={this.state.value}
items={this.state.unitedStates}
getItemValue={(item) => item.name}
onSelect={(value, item) => {
// set the menu to only the selected item
this.setState({ unitedStates: [ item ] })
this.setState({ value, unitedStates: [ item ] })
// or you could reset it to a default list again
// this.setState({ unitedStates: getStates() })
}}
onChange={(event, value) => {
this.setState({loading: true})
this.setState({ value, loading: true })
fakeRequest(value, (items) => {
this.setState({ unitedStates: items, loading: false })
})
Expand Down
6 changes: 4 additions & 2 deletions examples/custom-menu/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ let App = React.createClass({

getInitialState () {
return {
value: '',
unitedStates: getStates(),
loading: false
}
Expand All @@ -21,13 +22,14 @@ let App = React.createClass({
letter of the alphabet.
</p>
<Autocomplete
value={this.state.value}
labelText="Choose a state from the US"
inputProps={{name: "US state"}}
items={this.state.unitedStates}
getItemValue={(item) => item.name}
onSelect={() => this.setState({ unitedStates: [] }) }
onSelect={value => this.setState({ value, unitedStates: [] }) }
onChange={(event, value) => {
this.setState({loading: true})
this.setState({ value, loading: true })
fakeRequest(value, (items) => {
this.setState({ unitedStates: items, loading: false })
})
Expand Down
7 changes: 6 additions & 1 deletion examples/static-data/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ import { getStates, matchStateToTerm, sortStates, styles } from '../../lib/utils
import Autocomplete from '../../lib/index'

let App = React.createClass({
getInitialState() {
return { value: 'Ma' }
},
render () {
return (
<div>
Expand All @@ -14,13 +17,15 @@ let App = React.createClass({
</p>

<Autocomplete
initialValue="Ma"
value={this.state.value}
labelText="Choose a state from the US"
inputProps={{name: "US state"}}
items={getStates()}
getItemValue={(item) => item.name}
shouldItemRender={matchStateToTerm}
sortItems={sortStates}
onChange={(event, value) => this.setState({ value })}
onSelect={value => this.setState({ value })}
renderItem={(item, isHighlighted) => (
<div
style={isHighlighted ? styles.highlightedItem : styles.item}
Expand Down
36 changes: 16 additions & 20 deletions lib/Autocomplete.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ let _debugStates = []
let Autocomplete = React.createClass({

propTypes: {
initialValue: React.PropTypes.any,
value: React.PropTypes.any,
onChange: React.PropTypes.func,
onSelect: React.PropTypes.func,
shouldItemRender: React.PropTypes.func,
Expand All @@ -19,6 +19,7 @@ let Autocomplete = React.createClass({

getDefaultProps () {
return {
value: '',
inputProps: {},
labelText: '',
onChange () {},
Expand All @@ -42,7 +43,6 @@ let Autocomplete = React.createClass({

getInitialState () {
return {
value: this.props.initialValue || '',
isOpen: false,
highlightedIndex: null,
}
Expand Down Expand Up @@ -92,11 +92,7 @@ let Autocomplete = React.createClass({

handleChange (event) {
this._performAutoCompleteOnKeyUp = true
this.setState({
value: event.target.value,
}, () => {
this.props.onChange(event, this.state.value)
})
this.props.onChange(event, event.target.value)
},

handleKeyUp () {
Expand Down Expand Up @@ -151,17 +147,17 @@ let Autocomplete = React.createClass({
else {
// text entered + menu item has been highlighted + enter is hit -> update value to that of selected menu item, close the menu
var item = this.getFilteredItems()[this.state.highlightedIndex]
var value = this.props.getItemValue(item)
this.setState({
value: this.props.getItemValue(item),
isOpen: false,
highlightedIndex: null
}, () => {
//this.refs.input.focus() // TODO: file issue
this.refs.input.setSelectionRange(
this.state.value.length,
this.state.value.length
value.length,
value.length
)
this.props.onSelect(this.state.value, item)
this.props.onSelect(value, item)
})
}
},
Expand All @@ -179,21 +175,21 @@ let Autocomplete = React.createClass({

if (this.props.shouldItemRender) {
items = items.filter((item) => (
this.props.shouldItemRender(item, this.state.value)
this.props.shouldItemRender(item, this.props.value)
))
}

if (this.props.sortItems) {
items.sort((a, b) => (
this.props.sortItems(a, b, this.state.value)
this.props.sortItems(a, b, this.props.value)
))
}

return items
},

maybeAutoCompleteText () {
if (this.state.value === '')
if (this.props.value === '')
return
var { highlightedIndex } = this.state
var items = this.getFilteredItems()
Expand All @@ -203,13 +199,13 @@ let Autocomplete = React.createClass({
items[highlightedIndex] : items[0]
var itemValue = this.props.getItemValue(matchedItem)
var itemValueDoesMatch = (itemValue.toLowerCase().indexOf(
this.state.value.toLowerCase()
this.props.value.toLowerCase()
) === 0)
if (itemValueDoesMatch) {
var node = this.refs.input
var setSelection = () => {
node.value = itemValue
node.setSelectionRange(this.state.value.length, itemValue.length)
node.setSelectionRange(this.props.value.length, itemValue.length)
}
if (highlightedIndex === null)
this.setState({ highlightedIndex: 0 }, setSelection)
Expand Down Expand Up @@ -237,12 +233,12 @@ let Autocomplete = React.createClass({
},

selectItemFromMouse (item) {
var value = this.props.getItemValue(item);
this.setState({
value: this.props.getItemValue(item),
isOpen: false,
highlightedIndex: null
}, () => {
this.props.onSelect(this.state.value, item)
this.props.onSelect(value, item)
this.refs.input.focus()
this.setIgnoreBlur(false)
})
Expand Down Expand Up @@ -271,7 +267,7 @@ let Autocomplete = React.createClass({
top: this.state.menuTop,
minWidth: this.state.menuWidth,
}
var menu = this.props.renderMenu(items, this.state.value, style)
var menu = this.props.renderMenu(items, this.props.value, style)
return React.cloneElement(menu, { ref: 'menu' })
},

Expand Down Expand Up @@ -318,7 +314,7 @@ let Autocomplete = React.createClass({
onKeyDown={(event) => this.handleKeyDown(event)}
onKeyUp={(event) => this.handleKeyUp(event)}
onClick={this.handleInputClick}
value={this.state.value}
value={this.props.value}
id={this.id}
/>
{this.state.isOpen && this.renderMenu()}
Expand Down
40 changes: 31 additions & 9 deletions lib/__tests__/Autocomplete-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ chai.use(chaiEnzyme());
function AutocompleteComponentJSX (extraProps) {
return (
<Autocomplete
initialValue=''
labelText="Choose a state from the US"
inputProps={{name: "US state"}}
getItemValue={(item) => item.name}
Expand Down Expand Up @@ -49,11 +48,11 @@ describe('Autocomplete acceptance tests', () => {
expect(autocompleteWrapper.instance().refs.menu).to.exist;
});

it('should show results when partial match is typed in', () => {
it('should show results when value is a partial match', () => {

// Render autocomplete results upon partial input match
expect(autocompleteWrapper.ref('menu').children()).to.have.length(50);
autocompleteInputWrapper.simulate('change', { target: { value: 'Ar' } });
autocompleteWrapper.setProps({ value: 'Ar' });
expect(autocompleteWrapper.ref('menu').children()).to.have.length(6);

});
Expand All @@ -71,6 +70,27 @@ describe('Autocomplete acceptance tests', () => {

// Event handler unit tests

describe('Autocomplete keyPress-><character> event handlers', () => {

var autocompleteWrapper = mount(AutocompleteComponentJSX({}));
var autocompleteInputWrapper = autocompleteWrapper.find('input');

it('should pass updated `input.value` to `onChange` and replace with `props.value`', done => {

let value = '';
autocompleteWrapper.setProps({ value, onChange(_, v) { value = v; } });

autocompleteInputWrapper.get(0).value = 'a';
autocompleteInputWrapper.simulate('keyPress', { key : 'a', keyCode: 97, which: 97 });
autocompleteInputWrapper.simulate('change');

expect(autocompleteInputWrapper.get(0).value).to.equal('');
expect(value).to.equal('a');
done();
});

});

describe('Autocomplete kewDown->ArrowDown event handlers', () => {

var autocompleteWrapper = mount(AutocompleteComponentJSX({}));
Expand Down Expand Up @@ -173,32 +193,34 @@ describe('Autocomplete kewDown->Enter event handlers', () => {
});

it('should close menu if input has focus but no item has been selected and then the Enter key is hit', () => {
let value = '';
autocompleteWrapper.setState({'isOpen': true});
autocompleteInputWrapper.simulate('focus');
autocompleteInputWrapper.simulate('change', { target: { value: '' } });
autocompleteWrapper.setProps({ value, onSelect(v) { value = v; } });

// simulate keyUp of backspace, triggering autocomplete suggestion on an empty string, which should result in nothing highlighted
autocompleteInputWrapper.simulate('keyUp', { key : 'Backspace', keyCode: 8, which: 8 });
expect(autocompleteWrapper.state('highlightedIndex')).to.be.null;

autocompleteInputWrapper.simulate('keyDown', { key : 'Enter', keyCode: 13, which: 13 });

expect(autocompleteWrapper.state('value')).to.equal('');
expect(value).to.equal('');
expect(autocompleteWrapper.state('isOpen')).to.be.false;

});

it('should update input value from selected menu item and close the menu', () => {
it('should invoke `onSelect` with the selected menu item and close the menu', () => {
let value = 'Ar';
autocompleteWrapper.setState({'isOpen': true});
autocompleteInputWrapper.simulate('focus');
autocompleteInputWrapper.simulate('change', { target: { value: 'Ar' } });
autocompleteWrapper.setProps({ value, onSelect(v) { value = v; } });

// simulate keyUp of last key, triggering autocomplete suggestion + selection of the suggestion in the menu
autocompleteInputWrapper.simulate('keyUp', { key : 'r', keyCode: 82, which: 82 });

// Hit enter, updating state.value with the selected Autocomplete suggestion
autocompleteInputWrapper.simulate('keyDown', { key : 'Enter', keyCode: 13, which: 13 });
expect(autocompleteWrapper.state('value')).to.equal('Arizona');
expect(value).to.equal('Arizona');
expect(autocompleteWrapper.state('isOpen')).to.be.false;

});
Expand Down Expand Up @@ -238,7 +260,7 @@ describe('Autocomplete#renderMenu', () => {

it('should return a menu ReactComponent with a subset of children when partial match text has been entered', () => {
// Input 'Ar' should result in 6 items in the menu, populated from autocomplete.
autocompleteInputWrapper.simulate('change', { target: { value: 'Ar' } });
autocompleteWrapper.setProps({ value: 'Ar' });

var autocompleteMenu = autocompleteWrapper.instance().renderMenu();
expect(autocompleteMenu.props.children.length).to.be.equal(6);
Expand Down