El elemento de entrada no parece enfocarse al hacer clic en un botón en el primer intento, pero luego funciona. No estoy seguro de por qué no se está enfocando en el primer intento, ¿alguien puede ayudar?
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")); Establecer el enfoque en el primer clic a través de React no funciona porque el elemento de input todavía está deshabilitado.
Convierta los elementos de span en elementos de label y vincúlelos a su respectivo elemento de input mediante el atributo id . Cuando se hace clic en la etiqueta de las entradas, las entradas reciben el foco.
Ejemplo:
<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> En mi opinión, esto es un poco pirateado, pero si desea usar React ref para establecer el enfoque programáticamente, puede usar setTimeout con una devolución de llamada para establecer el enfoque. El uso de setTimeout colocará la devolución de llamada al final de la cola de eventos y se procesará después de que se haya procesado la actualización del estado de React y se haya vuelto a procesar el componente, lo que hace que la entrada sea enfocable.
Ejemplo:
<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")); Problema: Aquí el problema es que llamas a setConfig({ ...config, config1: false }), AND ref1.current.focus(); en un solo render así que ref1.current.focus(); este enfoque no puede entender los cambios de estado anteriores.
Solución: Entonces, la solución es que necesita un setConfig({ ...config, config1: false }), esta llamada primero y luego llame a ref1.current.focus(); . Aquí la posible solución es usar setTimeout o promise.all . Aquí usopromise.all promise.all es un enfoque comparativamente bueno que usar setTimeout
Puede consultar aquí lo que hace promis.all: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all