I have two input fields in my component:
const MyComponent = () => {
const onKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
if (event.key === 'ArrowLeft') {
ref.current?.setSelectionRange(value.length, value.length)
ref.current?.focus()
}
}
const [value, setValue] = useState('1234')
const ref = useRef<HTMLInputElement>(null)
return (
<>
<input
onChange={({target}) => setValue(target.value)}
value={value}
ref={ref}
type={'text'}/>
<input
onKeyDown={onKeyDown}
type={'text'}/>
</>
)
}
When i hit the left arrow-key on the second input, the first input should be focused, and the cursor should be at the end of the input text.
But the cursor is at the wrong place. Why is this not working?
CodePudding user response:
Was the cursor moving one position to the left of the last character?
Interestingly, when using onKeyUp
(the release of the key) rather than onKeyDown
the issue seems to go away.
https://codesandbox.io/s/cursor-end-of-input-onkeyup-x6ekke?file=/src/App.js
import { useRef, useState } from "react";
const MyComponent = () => {
const [value, setValue] = useState("1234");
const ref = useRef(null);
const onKeyUp = (event) => {
if (event.key === "ArrowLeft") {
const textInput = ref.current;
const len = value.length;
textInput.setSelectionRange(len, len);
textInput.focus();
}
};
return (
<>
<input
onChange={({ target }) => setValue(target.value)}
value={value}
ref={ref}
type={"text"}
/>
<input onKeyUp={onKeyUp} type={"text"} />
</>
);
};
export default MyComponent;
My guess is that because onKeyUp
naturally follows onKeyDown
, when we receive only the onKeyDown
event in React, per your example code, the following sequence occurs (or generally speaking something like this is happening):
- Our cursor is moved to the very end of the text input. (handled in
onKeyDown
handler). - The text input receives focus (also handled in
onKeyDown
handler). - The release of the left arrow key causes
onkeyup
event to run in the DOM (we haven't done anything to handle this in React) while the focus is now on the text input as a result of ouronKeyDown
handler that ran previously. The default behavior of pressing and releasing the left arrow key while focused on the input is to move the cursor one position to the left, placing it one position from the end of the input text.
I wasn't able to find a good resource or previous answer to clearly explain this, but hopefully that helps with an initial line of inquiry if you want to learn more.
Sticking with your use of onKeyDown
, I had to throw in event.preventDefault()
on your event handler and then things seemed to work as expected. setTimeout()
appears to be another workaround. My guess is that these workarounds effectively block the browser from firing the key down and up events altogether or delay our handler from running until after the native events have fired, respectively.
https://codesandbox.io/s/cursor-end-of-input-example-h1yrdr?file=/src/App.js
import { useRef, useState } from "react";
const MyComponent = () => {
const [value, setValue] = useState("1234");
const ref = useRef(null);
const onKeyDown = (event) => {
if (event.key === "ArrowLeft") {
/*
* 1. setTimout() option...
*/
setTimeout(() => {
const textInput = ref.current;
const len = value.length;
textInput.setSelectionRange(len, len);
textInput.focus();
}, 0);
/*
* 2. Alternatively, using event.preventDefault()...
*/
// event.preventDefault();
// const textInput = ref.current;
// const len = value.length;
// textInput.setSelectionRange(len, len);
// textInput.focus();
// }
}
};
return (
<>
<input
onChange={({ target }) => setValue(target.value)}
value={value}
ref={ref}
type={"text"}
/>
<input onKeyDown={onKeyDown} type={"text"} />
</>
);
};
export default MyComponent;