I have a parent component that tracks an array, rendering a child component that renders and allows edits to the array:
const Parent = () => {
let [items, setItems] = useState([]);
return html`<${Child} items=${items} setItems=${setItems} />`;
}
In this child component, the existing items are editable and additionally there is a "pending item" input for adding a new item to the array:
const Child = ({items,setItems}) => {
let [pendingId, setPendingId] = useState(null);
if (!pendingId) {
setPendingId(pendingId = Math.random().toFixed(20).slice(2));
}
return html`<div class="item-editor">
${items.map((item,idx) => html`<input key=${item._id}
value=${item.val}
oninput=${evt => {
let val = evt.target.value,
_items = [...items];
_items.splice(idx, 1, {...item,val});
setItems(_items);
}}
/>`)}
<input key=${pendingId} placeholder="type to add an item"
oninput=${evt => {
let val = evt.target.value;
_items = [...items];
_items.push({_id:pendingId, val});
setItems(_items);
setPendingId(null);
}}
/>
</div>`;
};
The problem is this: when using the code as written above, the key=${pendingId} input gets completely replaced in the DOM as soon as the user enters one letter.
Because that "pending" identifier is passed along into the new item my expectation was that, on the next render, the focused input should remain in the DOM and get reconciled with the lastmost of the newly rendered key=${item._id} inputs.
But instead, the input ends up getting removed from the DOM and the user's focus is lost!
Workaround
If I take the two setters that together add the pending item into the array and reset the pending id:
setItems(_items);
setPendingId(null);
And simply re-order the calls so that the child's pendingId state gets set before the parent's items state like so:
setPendingId(null);
setItems(_items);
Then the input is reconciled as expected, i.e. the user's focus is retained when the new input is inserted after the one they are using!
To reproduce
This code is available ready-to-run (plus some additional logging) at https://codesandbox.io/s/74jll4q4v1.
- Leave the "Set child state before parent state." configuration unchecked.
- Start typing in the placeholder text input
Expected results:
Despite a new placeholder getting inserted, the keyboard focus should be retained, and you should be able to keep typing. (And, if you check the "set child state before…" box it works this way!)
Actual results:
When the new placeholder is inserted, keyboard focus is completely lost. Debug logs indicate that the original placeholder input has been removed from the DOM, and both of the inputs now in the DOM are brand new.
I have a parent component that tracks an array, rendering a child component that renders and allows edits to the array:
In this child component, the existing items are editable and additionally there is a "pending item" input for adding a new item to the array:
The problem is this: when using the code as written above, the
key=${pendingId}input gets completely replaced in the DOM as soon as the user enters one letter.Because that "pending" identifier is passed along into the new item my expectation was that, on the next render, the focused input should remain in the DOM and get reconciled with the lastmost of the newly rendered
key=${item._id}inputs.But instead, the input ends up getting removed from the DOM and the user's focus is lost!
Workaround
If I take the two setters that together add the pending item into the array and reset the pending id:
And simply re-order the calls so that the child's
pendingIdstate gets set before the parent'sitemsstate like so:Then the input is reconciled as expected, i.e. the user's focus is retained when the new input is inserted after the one they are using!
To reproduce
This code is available ready-to-run (plus some additional logging) at https://codesandbox.io/s/74jll4q4v1.
Expected results:
Despite a new placeholder getting inserted, the keyboard focus should be retained, and you should be able to keep typing. (And, if you check the "set child state before…" box it works this way!)
Actual results:
When the new placeholder is inserted, keyboard focus is completely lost. Debug logs indicate that the original placeholder input has been removed from the DOM, and both of the inputs now in the DOM are brand new.