I'm using react-select
and react-final-form
for conditional dropdowns, where options for the second select are provided by a <PickOptions/>
component based on the value of the first select (thanks to this SO answer).
Here is the component:
/** Changes options and clears field B when field A changes */
const PickOptions = ({ a, b, optionsMap, children }) => {
const aField = useField(a, { subscription: { value: 1 } });
const bField = useField(b, { subscription: {} });
const aValue = aField.input.value.value;
const changeB = bField.input.onChange;
const [options, setOptions] = React.useState(optionsMap[aValue]);
React.useEffect(() => {
changeB(undefined); // clear B
setOptions(optionsMap[aValue]);
}, [aValue, changeB, optionsMap]);
return children(options || []);
};
It clears the second select when the value of the first one changes by changeB(undefined)
. I've also set the second select to the first option in an array by passing initialValue
. As I need to initialize the values from the state, I ended up with the following code:
initialValue={
this.state.data.options[index] &&
this.state.data.options[index].secondOption
? this.state.data.options[index]
.secondOption
: options.filter(
option => option.type === "option"
)[0]
}
But it doesn't work. Initial values from the state are not being passed to the fields rendered by <PickOptions/>
. If I delete changeB(undefined)
from the component, the values are passed but then the input value of the second select is not updated, when the value of the first select changes (even though the options have been updated). Here is the link to my codesandbox.
How can I fix it?
I was able to get this to work by taking everything that is mapped by the fields.map()
section and wrapping it in it's own component to ensure that each of them have separate states. Then I just put the changeB(undefined)
function in the return call of the useEffect
hook to clear the secondary selects after the user selects a different option for the first select like so:
React.useEffect(() => {
setOptions(optionsMap[aValue]);
return function cleanup() {
changeB(undefined) // clear B
};
}, [aValue, changeB, optionsMap]);
You can see how it works in this sandbox: React Final Form - Clear Secondary Selects.
To change the secondary select fields, you will need to pass an extra prop to PickOptions
for the type of option the array corresponds to. I also subscribe and keep track of the previous bValue
to check if it exists in the current bValueSet
array. If it exists, we leave it alone, otherwise we update it with the first value in its corresponding optionType
array.
// subscibe to keep track of previous bValue
const bFieldSubscription = useField(b, { subscription: { value: 1 } })
const bValue = bFieldSubscription.input.value.value
React.useEffect(() => {
setOptions(optionsMap[aValue]);
if (optionsMap[aValue]) {
// set of bValues defined in array
const bValueSet = optionsMap[aValue].filter(x => x.type === optionType);
// if the previous bValue does not exist in the current bValueSet then changeB
if (!bValueSet.some(x => x.value === bValue)) {
changeB(bValueSet[0]); // change B
}
}
}, [aValue, changeB, optionsMap]);
Here is the sandbox for that method: React Final Form - Update Secondary Selects.
I also changed your class component into a functional because it was easier for me to see and test what was going on but it this method should also work with your class component.