React input loses caret position in onChange when target.value is not used

57 views Asked by At

I am trying to intercept key events from a input field in React. I want to replace every key with a different one and keep the caret position where it is. Now, I have tried onChange and onKeyDown listeners.

If I use onChange like below, input field keeps its caret in its place.

      <Input
        type="text"
        placeholder="Kelime"
        onChange={(hadise) => {
          hususlar.kelimeTebdiliHalinde(hadise.target.value);
        }}
        value={hususlar.kelime}
      />

However, if I try to do something like below, caret jumps to the end of input field.

      <Input
        type="text"
        placeholder="Kelime"
        onChange={(hadise) => {
          const metin = hadise.target.value + "3";
          hususlar.kelimeTebdiliHalinde(metin);
        }}
        value={hususlar.kelime}
      />

I have started to believe, this target.value is not a simple string or React is doing something in onChange and caret position is kept as it is when target.value is touched.

In case of onKeyDown, I am replacing the key and adding it to string, then changing the state to redraw input field. This also causes to lose caret's position.

I have tried to find target.value's type but I could not.

I can solve this issue with keeping caret's position in a ref and using it to update caret position in a useEffect but I don't want to this. I want to understand its cause and solve it in a better way.

1

There are 1 answers

2
mhredox On

By default, when you change the value of a text input, the cursor automatically sets itself to the end of the text. Since (most people) write from left to right, it makes sense to set the cursor to the very end after any input.

When you add the character "3" to the end of the output from event.target.value, you are essentially accomplishing the same thing as if the user themselves had typed the character "3".

This behavior can be modified using input.setSelectionRange(). However, in your case there would be a few extra steps. Firstly, you would need a ref containing the input DOM element. Then, inside the onChange handler, you need to call inputRef.current.setSelectionRange(position, position). If you always wanted the cursor to be 1 character before the end of the output, this would be simple:

  <Input
    ref={inputRef} // This ref must be added to your component
    type="text"
    placeholder="Kelime"
    onChange={(hadise) => {
      const metin = hadise.target.value + "3";
      const pos = Math.max(metin.length - 1, 0); // Position cant be negative
      inputRef.current.setSelectionRange(pos, pos); // Selection start and end are both the same
      hususlar.kelimeTebdiliHalinde(metin);
    }}
    value={hususlar.kelime}
  />

If you need to put it at a specific position, then you just need to keep track of whatever position you want with a piece of state (not a ref). You could update the state in the onChange handler as needed.