Solution for caret jumping in React inputs
What are we solving?
Let's say that we have a controled input and, on change, we are updating the input value by replacing " "
(spaces) with -
symbols.
The component looks like this:
CustomInput.jsx | |
---|---|
Now, if we type in Hello World
, it will get formatted as Hello-World
.
Let's say that we want it to say "Hello React World" instead. We position our cursor caret behind letter o
, and type <space>React
.
The problem is that as soon as we enter the first character, in this situation <space>
, the caret jumps to the end of the input value. If we just continued typing, input value would be Hello--WorldReact
instead of Hello-React-World
.
Why does this happen?
To solve this problem, it's necessary to understand that this has nothing to do with React. The same problem would occur if you used plain JavaScript + HTML.
The reason why this happens is because we are injecting a modified value in a DOM input (the one where we replace " "
with -
). When that happens, the input moves caret to the end.
Preventing jumps
This can be fixed by saving the caret position before changing the input value, and restoring it afterwards.
We can do that in two steps:
- Saving caret position (
selectionRange
) to state. - Updating input's
selectionStart
andselectionEnd
values.
1. Saving caret position
First we need to add state for storing selectionRange and then, store it in handleChange
function.
2. Updating selectionRange
Now, we need to restore caret position.
Firstly, add a ref to the input, and then use it to access input element and set it's selectionRange. To set the selectionRange, we will use the useLayoutEffect
hook from React.
Tip
useLayoutEffect
is very similar to useEffect
. The difference is that useLayoutEffect
makes sure that any state updates or logic are processed before the browser repaints the screen. By using useLayoutEffect
instead of useEffect
, we are preventing any potential screen flickers.
That's it! Now the caret is no longer jumping to the end.