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