app/assets/components/common/RFReactSelect.js
import React from "react";
import PropTypes from "prop-types";
import Select, { Creatable } from "react-select";
import classnames from "classnames";
RFReactSelect.defaultProps = {
multi: false,
className: "",
creatable: false
};
RFReactSelect.propTypes = {
input: PropTypes.shape({
name: PropTypes.string.isRequired,
value: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
PropTypes.array
]).isRequired,
onBlur: PropTypes.func.isRequired,
onChange: PropTypes.func.isRequired,
onFocus: PropTypes.func.isRequired
}).isRequired,
options: PropTypes.array.isRequired,
multi: PropTypes.bool,
className: PropTypes.string,
creatable: PropTypes.bool
};
export default function RFReactSelect({
input,
options,
multi,
className,
meta: { touched, error },
creatable,
onNewOptionClick
}) {
const { name, value, onBlur, onChange, onFocus } = input;
const transformedValue = transformValue(value, options, multi, creatable);
let Component;
if (creatable) {
Component = Creatable;
} else {
Component = Select;
}
return (
<Component
valueKey="value"
name={name}
value={transformedValue}
multi={multi}
options={options}
onChange={
multi ? multiChangeHandler(onChange) : singleChangeHandler(onChange)
}
onBlur={() => onBlur(value)}
onFocus={onFocus}
className={classnames(className, { error: touched && error })}
onNewOptionClick={onNewOptionClick}
/>
);
}
/**
* onChange from Redux Form Field has to be called explicity.
*/
function singleChangeHandler(func) {
return function handleSingleChange(value) {
func(value ? value.value : "");
};
}
/**
* onBlur from Redux Form Field has to be called explicity.
*/
function multiChangeHandler(func) {
return function handleMultiHandler(values) {
func(values.map(value => value.value));
};
}
/**
* For single select, Redux Form keeps the value as a string, while React Select
* wants the value in the form { value: "grape", label: "Grape" }
*
* * For multi select, Redux Form keeps the value as array of strings, while React Select
* wants the array of values in the form [{ value: "grape", label: "Grape" }]
*/
function transformValue(value, options, multi, creatable) {
if (multi && typeof value === "string") return [];
if (creatable && !multi && isNaN(value)) {
return { label: value, value };
}
const filteredOptions = options.filter(option => {
return multi ? value.indexOf(option.value) !== -1 : option.value === value;
});
if (creatable && multi) {
value
.filter(idOrTitle => isNaN(idOrTitle))
.forEach(v => {
if (!filteredOptions.find(({ value }) => v === value)) {
filteredOptions.push({ label: v, value: v });
}
});
}
return multi ? filteredOptions : filteredOptions[0];
}