This question revamps a question on r/reactjs.
I have a controlled input
of which I can programmatically change the value. I would like to use .setSelectionRange()
in order to maintain the caret position within the input.
Except this doesn't work: each re-render automatically sets the selection range to the end of the input by default.
The problem is illustrated in this sandbox, where the author of the original question fixed the problem with a 10ms setTimeout()
delay.
How do I achieve this without using setTimeout()
or getSnapshotBeforeUpdate()
, which is not patible with Hooks?
This question revamps a question on r/reactjs.
I have a controlled input
of which I can programmatically change the value. I would like to use .setSelectionRange()
in order to maintain the caret position within the input.
Except this doesn't work: each re-render automatically sets the selection range to the end of the input by default.
The problem is illustrated in this sandbox, where the author of the original question fixed the problem with a 10ms setTimeout()
delay.
How do I achieve this without using setTimeout()
or getSnapshotBeforeUpdate()
, which is not patible with Hooks?
- FYI that the link to the sandbox is dead now. – Atticus29 Commented Jun 22, 2023 at 20:48
1 Answer
Reset to default 4The basic problem as I see it is that .setSelectionRange()
is being used in-line in the template, and should be wrapped in a useEffect()
.
I would also pull the selection handler out, just to be a bit tidier (as per handleDomainChange()
and handleSubmit()
).
useEffect for selection update
const[selection, setSelection] = useState()
useEffect(() => {
if (!selection) return; // prevent running on start
const {start, end} = selection;
inputEl.current.focus();
inputEl.current.setSelectionRange(start, end);
}, [selection])
const handleSelection = (e) => {
const start = inputEl.current.selectionStart;
const end = inputEl.current.selectionEnd;
... // other code within selection handler as per original
// inputEl.current.focus();
// // the line below doesn't work!
// // inputEl.current.setSelectionRange(start + e.native.length, end + e.native.length)
// //this one does, but is not good practice..
// setTimeout(
// () =>
// inputEl.current.setSelectionRange(
// start + e.native.length,
// end + e.native.length
// ),
// 10
// );
setSelection({start: start + e.native.length, end: end + e.native.length});
}
template change to call handleSelection()
<Picker
set="emojione"
onSelect={event => {
handleSelection(event)
}}
/>
Original code for reference
<Picker
set="emojione"
onSelect={e => {
const start = inputEl.current.selectionStart;
const end = inputEl.current.selectionEnd;
//const result = domainString.substring(0, start) + e.native + domainString.substring(end, domainString.length)
setDomainString(
prevString =>
prevString.substring(0, start) +
e.native +
prevString.substring(end, prevString.length)
);
setDomainsArray(
domainEndings.map(
ending =>
domainString.substring(0, start) +
e.native +
domainString.substring(end, domainString.length) +
ending
)
);
inputEl.current.focus();
// the line below doesn't work!
// inputEl.current.setSelectionRange(start + e.native.length, end + e.native.length)
//this one does, but is not good practice..
setTimeout(
() =>
inputEl.current.setSelectionRange(
start + e.native.length,
end + e.native.length
),
10
);
}}
/>