@availity/reactstrap-validation-select
Version:
Wrapper for react-select to work with availity-reactstrap-validation
253 lines (229 loc) • 6.82 kB
JavaScript
import React, { Component, createRef } from 'react';
import PropTypes from 'prop-types';
import qs from 'qs';
import get from 'lodash.get';
import AvSelect from './AvSelect';
import AvSelectField from './AvSelectField';
class AvResourceSelect extends Component {
constructor(props) {
super(props);
this.state = {};
this.state.previousOptions = [];
}
static propTypes = {
requestConfig: PropTypes.object,
resource: PropTypes.shape({
postGet: PropTypes.func,
post: PropTypes.func,
getResult: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
}).isRequired,
getResult: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
hasMore: PropTypes.oneOfType([PropTypes.bool, PropTypes.func]),
delay: PropTypes.number,
debounceTimeout: PropTypes.number,
label: PropTypes.node,
customerId: PropTypes.string,
parameters: PropTypes.object,
itemsPerPage: PropTypes.number,
onPageChange: PropTypes.func,
isDisabled: PropTypes.bool,
requiredParams: PropTypes.array,
watchParams: PropTypes.array,
// eslint-disable-next-line react/forbid-prop-types
cacheUniq: PropTypes.any,
additional: PropTypes.object,
graphqlConfig: PropTypes.shape({
type: PropTypes.string,
query: PropTypes.string,
}),
minCharsToSearch: PropTypes.number,
};
static defaultProps = {
delay: 350,
itemsPerPage: 50,
};
select = createRef();
loadOptions = (...args) => {
const [inputValue, , additional = {}] = args;
let { page } = additional;
const shouldReturnPreviousOptions =
inputValue.length > 0 &&
this.props.minCharsToSearch !== undefined &&
inputValue.length < this.props.minCharsToSearch;
if (shouldReturnPreviousOptions) {
return {
options: this.state.previousOptions,
hasMore: false,
additional: {
page,
limit: this.props.itemsPerPage,
},
};
}
let data;
let params;
if (this.props.graphqlConfig) {
data = {
variables: {
perPage: this.props.itemsPerPage,
filters: {
q: encodeURIComponent(inputValue),
...this.props.parameters,
},
},
};
if (this.props.graphqlConfig.query) {
data.query = this.props.graphqlConfig.query;
}
} else {
params = {
q: encodeURIComponent(inputValue),
limit: this.props.itemsPerPage,
customerId: this.props.customerId,
...this.props.parameters,
};
}
if (args.length === 3) {
if (this.props.graphqlConfig) {
data.variables.page = page;
} else {
params.offset = (page - 1) * this.props.itemsPerPage;
}
} else {
page = 1;
}
let requiredSatisfied =
!this.props.requiredParams || this.props.requiredParams.length === 0;
if (!requiredSatisfied) {
if (this.props.graphqlConfig) {
requiredSatisfied = this.props.requiredParams.every(param =>
get(data, `variables.filters.${param}`)
);
} else {
requiredSatisfied = this.props.requiredParams.every(
param => params[param]
);
}
}
if (this.props.isDisabled || !requiredSatisfied) {
return {
options: [],
hasMore: false,
};
}
if (this.props.onPageChange) this.props.onPageChange(inputValue, page);
let fetch;
if (this.props.graphqlConfig) {
fetch = () =>
this.props.resource.post(data, {
headers: {
'Content-Type': 'application/json',
},
...this.props.requestConfig,
});
} else {
fetch = () =>
this.props.resource.postGet(
qs.stringify(params, {
encode: false,
arrayFormat: 'repeat',
indices: false,
allowDots: true,
}),
{
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
...this.props.requestConfig,
}
);
}
return fetch()
.then(resp => {
if (!resp || !resp.data) {
throw new Error(`API returned an invalid response.`);
}
const getResult = this.props.getResult || this.props.resource.getResult;
let { hasMore } = this.props;
if (hasMore === undefined) {
if (this.props.graphqlConfig) {
hasMore = data =>
get(
data.data,
`${this.props.graphqlConfig.type}Pagination.pageInfo.hasNextPage`,
false
);
} else {
hasMore = ({ totalCount, limit, offset }) =>
totalCount > offset + limit;
}
}
const items =
(typeof getResult === 'function'
? getResult.call(this.props.resource, resp.data)
: resp.data[getResult]) || this.data;
hasMore = typeof hasMore === 'function' ? hasMore(resp.data) : hasMore;
if (!Array.isArray(items)) {
throw new TypeError(
`Expected data to be an array but got \`${typeof items}\`. Use the \`getData\` prop to specify how to get the data from the API response.`
);
}
this.setState({ previousOptions: items });
return {
options: items,
hasMore,
additional: {
...additional,
page: page + 1,
},
};
})
.catch(() => ({ options: [], hasMore: false }));
};
render() {
const Tag = this.props.label ? AvSelectField : AvSelect;
const {
delay,
debounceTimeout = delay,
itemsPerPage,
watchParams,
cacheUniq,
additional,
...rest
} = this.props;
let _cacheUniq = cacheUniq;
if (_cacheUniq === undefined && watchParams) {
const params = {
customerId: this.props.customerId,
...this.props.parameters,
};
_cacheUniq = watchParams.map(watchParam => params[watchParam]).join(',');
}
return (
<Tag
selectRef={this.select}
loadOptions={this.loadOptions}
pagination
raw
debounceTimeout={debounceTimeout}
cacheUniq={_cacheUniq}
additional={{
page: 1,
...additional,
}}
{...rest}
/>
);
}
}
const ucFirst = str => str && str.charAt(0).toUpperCase() + str.slice(1);
AvResourceSelect.create = defaults => {
const SpecificAvResourceSelect = props => (
<AvResourceSelect {...defaults} {...props} />
);
SpecificAvResourceSelect.displayName = `Av${ucFirst(
defaults.resource.defaultConfig.name
)}Select`;
return SpecificAvResourceSelect;
};
export default AvResourceSelect;