Using React Select with Redux Form
At FireHydrant we use Redux Form for all of our forms. It is extremely easy to build complex form logic with all sorts of added bonuses that make using it in our React/Redux front end a no brainer. Learn how FireHydrant uses Redux Form.
By Dylan Nielsen on 4/23/2019
At FireHydrant we use Redux Form for all of our forms. It is extremely easy to build complex form logic with all sorts of added bonuses that make using it in our React/Redux front end a no brainer. However, when we started using React Select for our select fields we started running into some issues. You are likely running into some of the same issues we did and so this blog post will help get you off the ground and integrating these two libraries together.
Managing React Select state with Redux Form
First things first, we need to set up React Select to communicate its state and have its value controlled by Redux Form. React Select needs to act as a controlled component with Redux Form acting as the state management tool that controls React Select. That means hooking into the props that React Select exposes and calling our Redux Form functions from there. Here is the very simple implementation to hook into React Select's onChange and onBlur props.
export const ReduxFormSelect = props => {
const { input, options } = props;
return (
<Select
{...input}
onChange={value => input.onChange(value)}
onBlur={() => input.onBlur(input.value)}
options={options}
/>
)
}
And calling this component looks something like this.
import React from 'react';
import { Field, reduxForm } from 'redux-form'
import ReduxFormSelect from './ReduxFormSelect'
const Form = props => {
const { handleSubmit } = props;
return (
<form onSubmit={handleSubmit}>
<Field name="currentUser" component={ReduxFormSelect} options={userOptions} />
</form>
)
}
export default reduxForm({ form: 'filter' })(Form);
A couple of important things to note right off the bat. We still need to include all of the additional input props that Redux form gives us so we will destructure those props into the Select component. We also need to overwrite the onChange and onBlur functions provided by React Select. We will use the Redux Form onChange function so that the value emitted by React Select can be stored in our store by Redux Form. For onBlur, we want to set the value of the select to the input.value
prop that Redux Form provides us.
Now you have React Select's state being managed by Redux Form! But wait, how do we populate the options of our select?
Creating options and setting Initial Values
When we used the Redux Form Field component in the last example it looked something like this.
<Field name="currentUser" component={ReduxFormSelect} options={userOptions} />
We were passing a userOptions variable to the select as an options
prop so that it can be populated. But what does that userOptions variable look like? An array of option objects with label and value keys.
const userOptions = [
{
label: 'Erika',
value: '4e4cf51f-b406-413a-ae46-2cf06c7aabff',
},
{
label: 'Julia',
value: 'edad97c7-f2dc-4198-91a9-8f20c7bc67b2',
},
{
label: 'Sarah',
value: '57d3578a-3583-4290-8bae-596a4da81a8d',
},
];
An important thing to note here is that we have to explicitly pass objects with both label and value keys so React Select can build the options menu. If you don't provide those two keys your options will not be created. Setting an initial value to the select is also straightforward, from wherever you are creating your initial values object you can pass the same label value object as the value for the field. And if you're using a multi select you can pass an array of those objects. Lets set our currentUser
field to initialize with Sarah.
export default reduxForm({
form: 'filter',
intialValues: {
currentUser: {
label: 'Sarah',
value: '57d3578a-3583-4290-8bae-596a4da81a8d',
},
}
})(FilterForm);
There you go! Now your form will initialize with a correct initial value that is properly selected inside of React Select.
This should get you off the ground and controlling React Select through your redux store. We have run into some other things that have proven a little challenging and we will walk through them below.
Other Gotchas
Submitting React Select values
Since we are hooking directly into the onChange handler to update our state we are getting a slightly different output when compared to using a regular select. With a regular select, the value passed into your form handler will be the value
attribute from the markup. React Select passes a little bit more information along:
{
label: 'Sarah',
value: '57d3578a-3583-4290-8bae-596a4da81a8d',
}
You might be tempted to change the onChange handler in our initial implementation to call onChange={value => input.onChange(value.value)}
but React Select needs the entire object to be able to know what you have selected, it's the same as setting the initial values above. So in your handleSubmit
function pull the value out of the object returned before submitting it.
const handleSubmit = values => {
const newValues = Object.assign({}, values, {
currentUser: values.currentUser.value
})
// The rest of your submission logic goes here
fetch('/filter', {
method: 'POST',
body: JSON.stringify(newValues),
headers:{
'Content-Type': 'application/json'
}
})
}
This way React Select will track what option you have selected to apply the correct styles and display the selected options.
Populating initial values from an API response
We want to set our initial values based off of a request to a form state that we have saved in our database. Maybe that form state is saved in our redux state too. How can we set our initial values of the form to match our API response? Redux Form allows you to pass a prop called initialValues into your reduxForm HoC and it will use that object to create its initial values. We use this in mapStateToProps all the time
const formatUserForSelect = user => ({
label: user.name,
value: user.id,
});
const mapStateToProps = (state, ownProps) => {
let initialValues = {};
if (state.user) {
initialValues.currentUser = formatUserForSelect(state.user);
}
return {
initialValues,
}
};
const reduxFilterForm = reduxForm({
form: 'filter',
enableReinitialize: true,
})(FilterForm);
export default connect(mapStateToProps)(reduxFilterForm);
If you are populating the form using an API response set `enableReinitialize` to `true`, without this Redux Form will not update your initial values after the component mounts even if they change. Also, note that we aren't passing in an initial values key to the reduxForm HoC, Redux Form can accept initialValues
as a prop from anywhere and will handle setting the initial values for you.
OnBlur handler breaks on mobile
With the current version of our onBlur handler, we are checking the input.value
prop to control the state of the select when the menu closes. Unfortunately, React Select calls onChange and onBlur simultaneously on mobile. This leads to a race condition where the input
prop has not updated by the time blur
tries to check the value. By adding a timeout and a recheck on the props inside the timeout we can ensure that the onChange function has updated the store.
Dispatching Changes
Another way to handle the changing fields of your form is to use Redux Form's actions and dispatch them yourself. We do this by using mapDispatchToProps
to dispatch a change
action.
import React from 'react';
import { Field, reduxForm, change } from 'redux-form';
export class FilterForm extends React.Component {
state = { showAdvancedForm: false };
componentDidUpdate = (prevProps, prevState) => {
const { dispatchChange, initialFilterValues, initialValues } = this.props;
const { showAdvancedForm } = this.state;
if (showAdvancedForm && !prevState.showAdvancedForm) {
dispatchChange('filter', 'date1', new Date().toISOString().slice(0, 10));
}
}
render = () => {
const { handleSubmit } = this.props;
const { showAdvancedForm } = this.state;
let advancedForm = '';
if (showAdvancedForm) {
advancedForm = (
<React.Fragment>
<div className="mb-3 mt-3">
<Field name="date1" component={Input} type="date" label="Date" />
</div>
</React.Fragment>
);
}
return (
<Form onSubmit={handleSubmit}>
<div className="mb-3">
<Field name="query" component={Input} type="text" label="Search Query" />
</div>
{advancedForm}
<div className="mb-3 text-right text-bottom">
<Button type="button" className="btn btn-outline-primary" size="sm" onClick={this.toggleAdvanceForm}>Show Advanced</Button>
<Button color="primary" size="sm" type="submit">Filter</Button>
</div>
</Form>
);
}
const mapDispatchToProps = dispatch => ({
dispatchChange: (formName, field, value) => dispatch(change(formName, field, value)),
});
const reduxFormFilterForm = reduxForm({
form: 'filter',
})(FilterForm);
const connectFilterForm = connect(null, mapDispatchToProps)(reduxFormFilterForm);
export default connectFilterForm;
Here whenever we toggle the advanced form we dispatch a change
action to prefill the date field to today.
I hope this was helpful, if you have any questions about how we've implemented this my Twitter handle is @nielsendylan.
You just got paged. Now what?
FireHydrant helps every team master incident response with straightforward processes that build trust and make communication easy.
See FireHydrant in action
See how our end-to-end incident management platform can help your team respond to incidents faster and more effectively.
Get a demo