Using react-select with redux-form

Using react-select with redux-form

While building an app for work I ran into a scenario that required a drop down box that is searchable and since we are using reactjs with material-ui, redux, and redux-form I have a few tools under my belt to get the job done however I still ran into a few issues so here is the component I'm supplying redux-form's Field to render a searchable dropdown box in react.

In addition to the options and placeholder properties this component is supplied to the redux-form Field to render the react-select dropdown box with some custom material-ui styling. The component takes the props from the redux-form <Field> and applies those properties to the react-select element.

The components being passed into the react-select element are where the real customization is happening.  These component here are used for styling and customizing the react-select dropdown input box, options, and placeholder.

The ternary statement on the value prop is checking the value of the input box and if it is only a string it finds the option in the options array and uses it because react-select's value requires an object { label: "Red", value: "r" } instead of just a string "Red". Its worth pointing this out because when I am displaying this form I am fetching data from my API to see if this question has been answered and if an answer exists, the value is provided to the component decorated with reduxForm(). My API sends "r" for the favoriteColor so when redux-form assigns initialValues to the favoriteColor field the value of the favoriteColor field is "r" so we use that value from the API to search through the options and prepopulate the react-select dropdown with the correct option based on the value from the API. This ternary statement is just the approach I took for pre populating the field with the redux-form initialValues. Your mileage may vary.

Enough rambling! To the code already!

Here is a CodeSandbox to play in and see it in action.

Edit react-select / redux-form - Material UI Example

SearchSelect.js Component

// @flow
import React, { PureComponent } from "react";
import { withStyles } from "@material-ui/core/styles";
import Select from "react-select";
import Typography from "@material-ui/core/Typography";
import TextField from "@material-ui/core/TextField";
import MenuItem from "@material-ui/core/MenuItem";

const styles = theme => ({
	selectWrapper: {
		display: "inline-block",
		verticalAlign: "top",
		maxWidth: 200,
		marginTop: 16,
		marginBottom: 8,
		marginLeft: 8,
		marginRight: 8,
	},
	input: {
		display: "flex",
		padding: 8,
		width: 190,
		height: 32,
	},
	valueContainer: {
		display: "flex",
		flex: 1,
		alignItems: "center",
	},
	noOptionsMessage: {
		fontSize: 16,
		padding: `${theme.spacing.unit}px ${theme.spacing.unit * 2}px`,
	},
	singleValue: {
		fontSize: 16,
	},
	placeholder: {
		position: "absolute",
		left: 2,
		fontSize: 16,
	},
});

const NoOptionsMessage = props => {
	return (
		<Typography
			color="textSecondary"
			className={props.selectProps.classes.noOptionsMessage}
			{...props.innerProps}
		>
			{props.children}
		</Typography>
	);
};

const inputComponent = ({ inputRef, ...props }) => {
	return <div ref={inputRef} {...props} />;
};

const Control = props => {
	return (
		<TextField
			InputProps={{
				inputComponent,
				inputProps: {
					className: props.selectProps.classes.input,
					ref: props.innerRef,
					children: props.children,
					...props.innerProps,
				},
			}}
		/>
	);
};

const Option = props => {
	return (
		<MenuItem
			buttonRef={props.innerRef}
			selected={props.isFocused}
			component="div"
			style={{
				fontWeight: props.isSelected ? 500 : 400,
			}}
			{...props.innerProps}
		>
			{props.children}
		</MenuItem>
	);
};

const Placeholder = props => {
	return (
		<Typography
			color="textSecondary"
			className={props.selectProps.classes.placeholder}
			{...props.innerProps}
		>
			{props.children}
		</Typography>
	);
};

const SingleValue = props => {
	return (
		<Typography
			className={props.selectProps.classes.singleValue}
			{...props.innerProps}
		>
			{props.children}
		</Typography>
	);
};

function ValueContainer(props) {
	return (
		<div className={props.selectProps.classes.valueContainer}>
			{props.children}
		</div>
	);
}

const components = {
	Option,
	Control,
	NoOptionsMessage,
	Placeholder,
	SingleValue,
	ValueContainer,
};

type SearchSelectProps = {
	classes: Object,
	options: Array<Object>,
	clearable: Boolean,
	placeholder: String,
};

class SearchSelect extends PureComponent<SearchSelectProps> {
	render() {
		const {
			classes,
			options,
			placeholder,
			input: { value, onChange, onBlur },
		} = this.props;

		return (
			<div className={classes.selectWrapper}>
				<Select
					{...this.props}
					value={
						typeof value === "string"
							? options.filter(option => option.value === value)
							: value
					}
					onChange={option => onChange(option.value)}
					onBlur={() => onBlur(value)}
					options={options}
					placeholder={placeholder}
					components={components}
				/>
			</div>
		);
	}
}

export default withStyles(styles)(SearchSelect);

MyForm.js

...
        <Field
          name="favoriteColor"
          component={SearchSelect}
          options={colors}
          clearable={true}
          placeholder="Favorite Color"
        />

...

MyForm is just making use of the SearchSelect component and the props given to Field will be passed to the SearchSelect component.

The above code is taken from the CodeSandbox example.  Check it out at the link above.