I have a React component that has multiple <select> (not react-select) elements. Only one <select> is rendered based on one of the props:
class SelectGroup extends Component {
...
render () {
let activeSelect
switch (this.props.foo) {
case 'select1':
activeSelect = <select id="select1">
<option value="1a" selected>1 A</option>
<option value="1b">1 B</option>
<option value="1c">1 C</option>
</select>
break
case 'select2':
activeSelect = <select id="select2">
<option value="2a" selected>2 A</option>
<option value="2b">2 B</option>
<option value="2c">2 C</option>
</select>
break
...
}
return activeSelect
}
...
}
(For our purposes, assume that SelectGroup is nested inside another component, which changes the prop foo when a button is clicked.)
What I want is for the first option for each <select> to be selected by default whenever we change which <select> is being rendered (whenever the prop foo is changed). As you can see, I gave the first option of each <select> the selected attribute which, according to this documentation, should make it the default option. However, that’s not the behavior I’m seeing. Here’s what happens:
select1is being displayed. I choose option1bfrom the dropdown.- I click the button to switch to
select2. - I expect for
2ato be selected because it has theselectedattribute. However,2bis selected instead. - I select
2cfrom the dropdown. - I click the button to switch back to
select1. - I expect for
1ato be selected. However,1cis selected instead.
It seems to be completely ignoring the selected attribute and instead it looks like it’s selecting the option from the list that corresponds to whichever option was selected before changing which <select> is rendered.
Oddly, if I remove the selected attribute from all <select> elements except select1, then select1 will behave as expected: when I switch to select1, it will always display option 1a regardless of what was selected before, but the option displayed by select2 will continue to depend on whatever was chosen before switching to it. So it’s as if selected only works if it is only used once, but I don’t understand why that is, since it is only being applied to one option per <select>.
How can I get the first option to be selected whenever I switch between which <select> is being rendered?
Advertisement
Answer
Try adding a unique key to each select:
switch (this.props.foo) {
case 'select1':
activeSelect = <select key="select1" id="select1">
<option value="1a" selected>1 A</option>
<option value="1b">1 B</option>
<option value="1c">1 C</option>
</select>
break
case 'select2':
activeSelect = <select key="select2" id="select2">
<option value="2a" selected>2 A</option>
<option value="2b">2 B</option>
<option value="2c">2 C</option>
</select>
break
...
This will force a complete rerender of the select. What is happening is most likely when you switch from one to the other, react’s VDOM kicks in and it modifies the existing select rather than replaces it. Due to (quite weird and archaic) DOM behaviors, the selected value of the old select is retained.
By using a unique key it will force React to completely rerender the whole node, throwing away any selected value state in memory in the process.
What you are doing is a bit weird btw — there are almost certainly better ways but I don’t know enough about your use case. You probably want to instead conditionally render them inline, which means in the internal representation of the component held by React, they are 2 separate nodes:
return <>
{this.props.foo === "select1" &&
<select id="select1">
<option value="1a" selected>1 A</option>
<option value="1b">1 B</option>
<option value="1c">1 C</option>
</select>
}
{this.props.foo === "select2" &&
<select id="select1">
<option value="1a" selected>1 A</option>
<option value="1b">1 B</option>
<option value="1c">1 C</option>
</select>
}
</>
You might think “how is this any different”. Well, the inactive ones will render false (which renders nothing). But that it renders “false” means that node is represented separately as that value and not stuffed into one, which is why reacts vdom is thinking it’s the exact same node.