react-select
Version:
A Select control built with and for ReactJS
168 lines (157 loc) • 5 kB
JavaScript
import React from 'react';
import Select from './Select';
import stripDiacritics from './utils/stripDiacritics';
let requestId = 0;
function initCache (cache) {
if (cache && typeof cache !== 'object') {
cache = {};
}
return cache ? cache : null;
}
function updateCache (cache, input, data) {
if (!cache) return;
cache[input] = data;
}
function getFromCache (cache, input) {
if (!cache) return;
for (let i = input.length; i >= 0; --i) {
let cacheKey = input.slice(0, i);
if (cache[cacheKey] && (input === cacheKey || cache[cacheKey].complete)) {
return cache[cacheKey];
}
}
}
function thenPromise (promise, callback) {
if (!promise || typeof promise.then !== 'function') return;
return promise.then((data) => {
callback(null, data);
}, (err) => {
callback(err);
});
}
const stringOrNode = React.PropTypes.oneOfType([
React.PropTypes.string,
React.PropTypes.node
]);
const Async = React.createClass({
propTypes: {
cache: React.PropTypes.any, // object to use to cache results, can be null to disable cache
ignoreAccents: React.PropTypes.bool, // whether to strip diacritics when filtering (shared with Select)
ignoreCase: React.PropTypes.bool, // whether to perform case-insensitive filtering (shared with Select)
isLoading: React.PropTypes.bool, // overrides the isLoading state when set to true
loadOptions: React.PropTypes.func.isRequired, // function to call to load options asynchronously
loadingPlaceholder: React.PropTypes.string, // replaces the placeholder while options are loading
minimumInput: React.PropTypes.number, // the minimum number of characters that trigger loadOptions
noResultsText: stringOrNode, // placeholder displayed when there are no matching search results (shared with Select)
onInputChange: React.PropTypes.func, // onInputChange handler: function (inputValue) {}
placeholder: stringOrNode, // field placeholder, displayed when there's no value (shared with Select)
searchPromptText: stringOrNode, // label to prompt for search input
searchingText: React.PropTypes.string, // message to display while options are loading
},
getDefaultProps () {
return {
cache: true,
ignoreAccents: true,
ignoreCase: true,
loadingPlaceholder: 'Loading...',
minimumInput: 0,
searchingText: 'Searching...',
searchPromptText: 'Type to search',
};
},
getInitialState () {
return {
cache: initCache(this.props.cache),
isLoading: false,
options: [],
};
},
componentWillMount () {
this._lastInput = '';
},
componentDidMount () {
this.loadOptions('');
},
componentWillReceiveProps (nextProps) {
if (nextProps.cache !== this.props.cache) {
this.setState({
cache: initCache(nextProps.cache),
});
}
},
focus () {
this.select.focus();
},
resetState () {
this._currentRequestId = -1;
this.setState({
isLoading: false,
options: [],
});
},
getResponseHandler (input) {
let _requestId = this._currentRequestId = requestId++;
return (err, data) => {
if (err) throw err;
if (!this.isMounted()) return;
updateCache(this.state.cache, input, data);
if (_requestId !== this._currentRequestId) return;
this.setState({
isLoading: false,
options: data && data.options || [],
});
};
},
loadOptions (input) {
if (this.props.onInputChange) {
let nextState = this.props.onInputChange(input);
// Note: != used deliberately here to catch undefined and null
if (nextState != null) {
input = '' + nextState;
}
}
if (this.props.ignoreAccents) input = stripDiacritics(input);
if (this.props.ignoreCase) input = input.toLowerCase();
this._lastInput = input;
if (input.length < this.props.minimumInput) {
return this.resetState();
}
let cacheResult = getFromCache(this.state.cache, input);
if (cacheResult) {
return this.setState({
options: cacheResult.options,
});
}
this.setState({
isLoading: true,
});
let responseHandler = this.getResponseHandler(input);
let inputPromise = thenPromise(this.props.loadOptions(input, responseHandler), responseHandler);
return inputPromise ? inputPromise.then(() => {
return input;
}) : input;
},
render () {
let { noResultsText } = this.props;
let { isLoading, options } = this.state;
if (this.props.isLoading) isLoading = true;
let placeholder = isLoading ? this.props.loadingPlaceholder : this.props.placeholder;
if (isLoading) {
noResultsText = this.props.searchingText;
} else if (!options.length && this._lastInput.length < this.props.minimumInput) {
noResultsText = this.props.searchPromptText;
}
return (
<Select
{...this.props}
ref={(ref) => this.select = ref}
isLoading={isLoading}
noResultsText={noResultsText}
onInputChange={this.loadOptions}
options={options}
placeholder={placeholder}
/>
);
}
});
module.exports = Async;