Search code examples
javascriptreactjsreact-hooksonfocus

Why is useRef focus not working in React?


In React, I want that if something is typed into a form, another form is immediately set to focus.

For example:

<html>
  <head>
    <title>React App</title>
    <script src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
    <script src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
    <script src="https://unpkg.com/[email protected]/babel.min.js"></script>
  </head>
 
  <body>
    <div id="mydiv"></div>
    <script type="text/babel"> 
                                                   //Now its basically like React
      function Inputs() {
        const [otp, setOtp] = React.useState([]); //Here are stored the Values of the numbers
        const itemsRef = React.useRef([]);       //Here are stored the refs of the numbers
        const setOtpfunction = (e, i) => {
          const key = parseInt(e.key);     //I first parse the key that i got from onKeyPress to a number 
          if (key) {
            let OTP = [...otp];
            OTP[i] = key;
            setOtp(OTP);                  //Then i update the numbers
            if (i < 5) {
              const nextForm = i + 1;     //Here i say that the next item schould be the item with the index + 1
              ref[i].focus();            //And here i set the focus. ref.current.focus() wouldnt work in this case
            }
          }
        };
        return (
          <div className="space-x-2">
            <input
              placeholder="first input"
              type="number"
              className="otpNumber"
              onChange={(e) => (e = null)} //that there is no error because there is no onChange function
              required
              onKeyPress={(e) => setOtpfunction(e, 0)}
              ref={(ref) => (itemsRef[0] = ref)}
              autoFocus
              value={otp[0] || ""}
            />
            <input
            placeholder="second input"
              type="number"
              className="otpNumber"
              onChange={(e) => (e = null)} //that there is no error because there is no onChange function
              onKeyPress={(e) => setOtpfunction(e, 1)}
              ref={(ref) => (itemsRef[1] = ref)}
              value={otp[1] || ""}
            />
            <input
              placeholder="third input"
              type="number"
              className="otpNumber"
              onChange={(e) => (e = null)} //that there is no error because there is no onChange function
              onKeyPress={(e) => setOtpfunction(e, 2)}
              ref={(ref) => (itemsRef[2] = ref)}
              value={otp[2] || ""}
            />
            <p>Now you can type in 1 number in the inputs and they are stored in UseState. But i want that if in the first input, one number is entered, it focuses the second input and so on</p>
          </div>
        );
      }
      class Render extends React.Component {
        render() {
          return (
            <div>
              <Inputs />
            </div>
          );
        }
      }
      ReactDOM.render(<Render />, document.getElementById("mydiv"));
    </script>
  </body>
</html>

The console's error was only a warning that you should compile your scripts for production. That's why I hid it.

I also tested it with document.getElemntbyId, which also didn´t work.

I hope you can understand what I mean. Thanks for any answers that can be provided!


Solution

  • I would rather make a single Input component, that notifies the parent when the user types something and updates the state accordingly:

    const OTP_LENGTH = 4;
    
    export default function OTPForm() {
      const [index, setIndex] = useState(0);
    
      function done() {
        if (index === OTP_LENGTH - 1) {
          console.log("All inputs filled");
          return;
        }
        setIndex(index + 1);
      }
    
      return (
        <form>
          {Array.from({ length: OTP_LENGTH }, (_, i) => (
            <Input key={i} isCurrent={i === index} done={done} />
          ))}
        </form>
      );
    }
    
    function Input({ isCurrent, done }) {
      const ref = useRef(null);
    
      useEffect(() => {
        if (isCurrent && ref.current) {
          ref.current.focus();
        }
      }, [isCurrent]);
    
      function onInput(event) {
        if (event.target.value.length > 1) {
          event.preventDefault();
          return;
        }
        if (event.target.value.length === 1) {
          done();
        }
      }
    
      return <input ref={ref} onInput={onInput} type="number"/>;
    }
    

    Codesandbox

    Up to you to expose proper onError / onSubmit handlers from the parent component.