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:
select1
is being displayed. I choose option 1b
from the dropdown.select2
.2a
to be selected because it has the selected
attribute. However, 2b
is selected instead.2c
from the dropdown.select1
.1a
to be selected. However, 1c
is 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?
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 removal & subsequent append when the condition flips. 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 remove and recreate the whole node completely, throwing away any browser state about the selected value that exists in memory (which is outside of Reacts purview and is a rare misalignment with React's mental model) 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 was treating it as the same root element.