The input element doesnt seem to get focused on click of a button on the first go, But later on it is working. Not sure why it is not being focused on the first go, can some one help
import React, { useEffect, useRef, useState } from "react";
import { render } from "react-dom";
import * as data from "./messages.json";
const App = () => {
return <Test {...{ alreadySetVal1: true, alreadySetVal2: true }} />;
};
const Test = ({ alreadySetVal1, alreadySetVal2 }) => {
const [input1, setInput1] = useState(alreadySetVal1 ? "Already Set" : "");
const [input2, setInput2] = useState(alreadySetVal2 ? "Already Set" : "");
const [config, setConfig] = useState({
config1: !!input1,
config2: !!input2
});
const ref1 = useRef(null);
const ref2 = useRef(null);
return (
<>
<div>
<input
disabled={config.config1}
value={input1}
ref={ref1}
type={config.config1 ? "text" : "password"}
onChange={(e) => setInput1(e.target.value)}
/>
<span
onClick={() => {
setInput1("");
setConfig({ ...config, config1: false });
ref1.current.focus();
}}
>
Enable Input 1
</span>
</div>
<div>
<input
disabled={config.config2}
value={input2}
ref={ref2}
type={config.config2 ? "text" : "password"}
onChange={(e) => setInput2(e.target.value)}
/>
<span
onClick={() => {
setInput2("");
setConfig({ ...config, config2: false });
ref2.current.focus();
}}
>
Enable Input 2
</span>
</div>
</>
);
};
render(<App messages={data.messages} />, document.getElementById("root"));
Setting the focus on the first click via React doesn't work because the input
element is currently still disabled.
Convert the span
elements to label
elements and link them to their respective input
element by id
attribute. When the label for inputs are clicked the inputs receive the focus.
Example:
<div>
<input
id="input1" // <-- add id attribute
disabled={config.config1}
value={input1}
type={config.config1 ? "text" : "password"}
onChange={(e) => setInput1(e.target.value)}
/>
<label
htmlFor="input1" // <-- label for specific input
onClick={() => {
setInput1("");
setConfig({ ...config, config1: false });
}}
>
Enable Input 1
</label>
</div>
<div>
<input
id="input2"
disabled={config.config2}
value={input2}
type={config.config2 ? "text" : "password"}
onChange={(e) => setInput2(e.target.value)}
/>
<label
htmlFor="input2"
onClick={() => {
setInput2("");
setConfig({ ...config, config2: false });
}}
>
Enable Input 2
</label>
</div>
This is a bit hackish IMO, but if you want to use the React ref to programatically set the focus then you can use a setTimeout
with a callback to set the focus. Using the the setTimeout
will place the callback at the end of the event queue and be processed after the React state update has been processed and the component rerendered, thus making the input focusable.
Example:
<div>
<input
disabled={config.config1}
value={input1}
ref={ref1}
type={config.config1 ? "text" : "password"}
onChange={(e) => setInput1(e.target.value)}
/>
<span
onClick={() => {
setInput1("");
setConfig({ ...config, config1: false });
setTimeout(() => {
ref1.current.focus();
});
}}
>
Enable Input 1
</span>
</div>
<div>
<input
disabled={config.config2}
value={input2}
ref={ref2}
type={config.config2 ? "text" : "password"}
onChange={(e) => setInput2(e.target.value)}
/>
<span
onClick={() => {
setInput2("");
setConfig({ ...config, config2: false });
setTimeout(() => {
ref2.current.focus();
})
}}
>
Enable Input 2
</span>
</div>
import React, { useEffect, useRef, useState } from "react";
import { render } from "react-dom";
import * as data from "./messages.json";
const App = () => {
return <Test {...{ alreadySetVal1: true, alreadySetVal2: true }} />;
};
const Test = ({ alreadySetVal1, alreadySetVal2 }) => {
const [input1, setInput1] = useState(alreadySetVal1 ? "Already Set" : "");
const [input2, setInput2] = useState(alreadySetVal2 ? "Already Set" : "");
const [config, setConfig] = useState({
config1: !!input1,
config2: !!input2
});
const ref1 = useRef(null);
const ref2 = useRef(null);
return (
<>
<div>
<input
disabled={config.config1}
value={input1}
ref={ref1}
type={config.config1 ? "text" : "password"}
onChange={(e) => setInput1(e.target.value)}
/>
<span
onClick={async () => {
setInput1("");
await Promise.all([
setConfig({ ...config, config1: false }),
]);
ref1.current.focus()
}}
>
Enable Input 1
</span>
</div>
<div>
<input
disabled={config.config2}
value={input2}
ref={ref2}
type={config.config2 ? "text" : "password"}
onChange={(e) => setInput2(e.target.value)}
/>
<span
onClick={async() => {
setInput2("");
await Promise.all([
setConfig({ ...config, config2: false }),
]);
ref2.current.focus();
}}
>
Enable Input 2
</span>
</div>
</>
);
};
render(<App messages={data.messages} />, document.getElementById("root"));
Problem: Here problem is you call setConfig({...config, config1: false }),
AND ref1.current.focus();
in a single render so ref1.current.focus();
this focus can't understand is previous state changes.
Solution: So, solution is you need a complete setConfig({...config, config1: false }),
this call first then call ref1.current.focus();
. Here possible solution is either you use setTimeout
or promise.all
. Here I use promise.all because promise.all
is comparatively good approach than using setTimeout
You can check here what promis.all do: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
Hope you understand Thank you
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.