I want to make an input
appear (go from display: none
to display: block
) when /
is pressed. Then the input
should be focused (the cursor should be in the input
).
import React from "react";
import { useState, useEffect, useRef, ChangeEvent } from "react";
import { createRoot } from "react-dom/client";
function App() {
const [inputValue] = useState("");
const [isInputFocused, setIsInputFocused] = useState(false);
const inputRef = useRef<HTMLInputElement | null>(null);
function handleInputChange(event: ChangeEvent<HTMLInputElement>) {
// some code
}
function handleInputBlur(event: ChangeEvent<HTMLInputElement>) {
setIsInputFocused(false);
}
function handleDocumentKeyDown(event: any) {
if (event.key === "/") {
event.preventDefault();
setIsInputFocused(true);
inputRef.current?.focus();
}
}
useEffect(() => {
inputRef.current?.focus();
document.addEventListener("keydown", handleDocumentKeyDown);
return () => {
document.removeEventListener("keydown", handleDocumentKeyDown);
};
}, []);
return (
<div id="keys" className={isInputFocused ? "active" : "inactive"}>
<input
ref={inputRef}
type="text"
onChange={handleInputChange}
onBlur={handleInputBlur}
value={inputValue}
/>
</div>
);
}
const root = createRoot(document.getElementById("root")!);
root.render(<App />);
This works as expected ... except the first time you press /
, the input
appears, but it's not focused (the cursor doesn't jump to the input
). You have to press /
again for the input to be focused.
Why is this? And how to change the code so that the input
is focused the first time you press /
?
Live example:
CodePudding user response:
As @ghybs said, setting state is async, so the actual re-render will happen at the next tick.
This means you can't immediately expect the input
field to be rendered as soon as you set the isInputFocused
state to true.
It needs to wait till the next tick.
Easiest way to wait till the next tick in React is using setTimeout
:
setIsInputFocused(true);
setTimeout(() => {
inputRef.current?.focus();
}, 0);
A more elegant solution is to wait for isInputFocused
state change and apply the focus action:
useEffect(() => {
if (isInputFocused) {
inputRef.current?.focus();
}
}, [isInputFocused]);