[10.0.0-alpha.2] Controlled form field rendered incorrectly after user input is "undone" via state
See original GitHub issueI have implemented a somewhat “recalcitrant” form field, which in certain cases refuses to leave itself with the value a user set. This select group is fully “controlled” in that it uses selected
/oninput
rather than defaultSelected
. This usually works except in one case:
If, after a user selects some other value, the value that I then render happens to be the value the group had before they selected, the user’s selection is left as-is.
E.g. say I have a select box with four options: none/red/green/blue. Whenever the user selects “none” I choose “blue” instead (bear with me; the paired select box code below is the real scenario):
- If the box starts out “red” and the user selects “none”, the state and the box end up “blue”
- If the box starts out “green” and the user selects “none”, the state and the box end up “blue”
- But if the box starts out “blue” and the user selects “none”, the state ends up “blue” but the box stays as “none”!
I suspect something might be going wrong in the diffing, i.e. when the diffing sees that the new vdom value is the same as the old vdom value and it doesn’t bother to update? (There may be some irony in me complaining about this, since I celebrated what was perhaps this very behavior in the context of #1294 at least as far as unspecified props and/or mutated children went…)
Sample code
This code below has both a SingleSelect
which demonstrates the issue above, as well as a DoubleSelect
component to show the plausibility of why I would actually do this: when the user clears out the first of a pair of select boxes, if there is a remaining value (in the second box) it should move into the first. It works fine if the boxes have different values, but when they start out both the same, they end up both displaying “[none]” even though when the first has a value.
const SingleSelect = () => {
const options = ['red', 'green', 'blue'];
let [keyA, setKeyA] = useState(null);
console.log("rendering:", keyA);
return html`<div>
<label>Color: </label>
<select oninput=${evt => {
let key = evt.target.value;
if (key === '-') {
setKeyA('blue');
} else {
setKeyA(key);
}
}}>
<option value="-" selected=${!keyA}>[none]</option>
${options.map(key => html`<option value=${key} selected=${key === keyA}>${key}</option>`)}
</select>
</div>`
};
const DoubleSelect = () => {
const options = ['red', 'green', 'blue'];
let [keyA, setKeyA] = useState(null);
let [keyB, setKeyB] = useState(null);
console.log("rendering:", keyA, keyB);
return html`<div>
<label>Colors: </label>
<select oninput=${evt => {
let key = evt.target.value;
if (key === '-') {
setKeyA(keyB);
setKeyB(null);
} else {
setKeyA(key);
}
}}>
<option value="-" selected=${!keyA}>[none]</option>
${options.map(key => html`<option value=${key} selected=${key === keyA}>${key}</option>`)}
</select>
<select oninput=${evt => {
let key = evt.target.value;
setKeyB((key !== '-') ? key : null);
}}>
<option value="-" selected=${!keyB}>[none]</option>
${options.map(key => html`<option value=${key} selected=${key === keyB}>${key}</option>`)}
</select>
</div>`
};
render(html`
<${DoubleSelect} />
<${SingleSelect} />
`, document.body);
Issue Analytics
- State:
- Created 4 years ago
- Comments:11 (10 by maintainers)
Top GitHub Comments
This might already be fixed with https://github.com/developit/preact/pull/1438 (I had a very similar issue: #1410)
It seems like the issue here is actually the use of
<option selected=..>
instead of<select value=..>
. Since Preact favors properties over attributes where they are defined,<select value="..">
is the preferred way of setting the value of a select.Here’s a modified version of the repro sandbox showing correct functionality after being converted to use
<select value>
: https://codesandbox.io/s/jjkkr7xpjw