9

I am trying to make the component that the focus moves to the next input when each letter inputted.

I think I need multiple ref like an array but I don't know about it.

It's a sample code for the question.

function PIN({length, onChange, value}){
  const inputEl = React.useRef(null);
  function handleChange(e){
    onChange(e);
    inputEl.current.focus(); 
  }
  return (
    <div>
      {
        new Array(length).fill(0).map((i)=>(
          <input type="text" ref={inputEl} onChange={handleChange} />
        ))
      }
    </div>
  )
}
5
  • Do you know in advance how many you need? Commented Apr 16, 2020 at 8:04
  • it's dynamic, it's around 1-16. It is not a static value. Commented Apr 16, 2020 at 8:07
  • 1
    So you want move to next when first letter will be input ? Commented Apr 16, 2020 at 8:11
  • 1
    possible duplicate: stackoverflow.com/questions/55995760/… Commented Apr 16, 2020 at 8:17
  • Does this answer your question? stackoverflow.com/a/60977523/11872246 Commented Apr 16, 2020 at 8:26

5 Answers 5

18

You can create multiple refs

function PIN({length, onChange, value}){
  const inputRefs = useMemo(() => Array(length).fill(0).map(i=> React.createRef()), []);
  const handleChange = index => (e) => {
    //onChange(e); // don't know about the logic of this onChange if you have multiple inputs
    if (inputRefs[index + 1]) inputRefs[index + 1].current.focus(); 
  }
  return (
    <div>
      {
        new Array(length).fill(0).map((inp, index)=>(
          <input type="text" ref={inputRefs[index]} onChange={handleChange(index)} />
        ))
      }
    </div>
  )
}

Sign up to request clarification or add additional context in comments.

3 Comments

Im just getting an error React Hook "React.useRef" cannot be called inside a callback not sure how this is the accepted anser.
If you are looking for the answer in typescript: const inputRefs: React.RefObject<HTMLInputElement>[] = useMemo( ...
Another way of generating an array with less HOF const inputRefs = useMemo(Array.from({length: 3}, x => React.createRef()), []) 2ality.com/2018/12/…
13

The ref on input is equivalent to a callback function. You can pass a method to him. The parameter received by this method is the input dom element, which you can store in an array.

import React from "react";
import "./styles.css";

export default function App() {
  const inputEl = React.useRef([]);
  function handleChange(i){
    inputEl.current[i+1].focus(); 
  }
  return (
    <div>
      {
        new Array(3).fill(0).map((n,i)=>(
          <input 
          key={i} 
          type="text" 
          ref={ref=>inputEl.current.push(ref)} 
          onChange={()=>handleChange(i)} 
          />
        ))
      }
    </div>
  )
}

4 Comments

perfect! hat's by far the best solution to this I've seen so far.
Doesn't this cause an ever-expanding array? Let's say you have 3 input elements. After first render the array length will be 3. But after a re-render, those 3 items will be pushed again. So yes, the first 3 array elements are intact, but the array length is now 6 (and then 9, 12 and so on...)
@targumon a cleanup function in useEffect can empty the ref array.
@AhashanAlamSojib sure, but why solve a problem if you can just avoid it? This is what I do: ref={ref => { if (ref) elements.current[key] = ref }} (elements is not an array but a simple JS object and key is the same one used anyway in the map function)
1

Re-rendering the component that holds the dynamic Refs list with a different number of refs raises an exception ("Rendered more hooks than during the previous render"), as you can see in this example:

https://codesandbox.io/s/intelligent-shannon-u3yo6?file=/src/App.js

You can create a new component that renders a single and holds it's own single ref, and use the parent element to manage the current focused input, and pass this data to you'r new component, for example.

1 Comment

this is what worked for me, easy and straight forward
0

In your inputs, you can pass a function to the ref parameter, this will allow you to store all of your refs in an array:

let myRefs = [];

const saveThisRef = (element) => {
  myRefs.push(element);
}

Then you can pass your function to each input you render:

<input type="text" ref={saveThisRef} onChange={handleChange} />

Then you can advance to the next input in the onChange handler:

// Find the index of the next element
const index = myRefs.indexOf(element) + 1;
// Focus it
if (index < myRefs.length) myRefs[index].focus();

Comments

-2

Here is an example that would actually work:

const { useState, useCallback, useEffect, useRef } = React;

const Pin = ({ length, onChange, value }) => {
  const [val, setVal] = useState(value.split(''));
  const [index, setIndex] = useState(0);
  const arr = [...new Array(length)].map(
    (_, index) => index
  );
  const myRefs = useRef(arr);
  const saveThisRef = (index) => (element) => {
    myRefs.current[index] = element;
  };
  function handleChange(e) {
    const newVal = [...val];
    newVal[index] = e.target.value;
    if (index < length - 1) {
      setIndex(index + 1);
    }
    setVal(newVal);
    onChange(newVal.join(''));
  }
  const onFocus = (index) => () => {
    const newVal = [...val];
    newVal[index] = '';
    setIndex(index);
    setVal(newVal);
    onChange(newVal.join(''));
  };
  useEffect(() => {
    if (index < myRefs.current.length) {
      myRefs.current[index].focus();
    }
  }, [index, length, myRefs]);
  return arr.map((index) => (
    <input
      type="text"
      ref={saveThisRef(index)}
      onChange={handleChange}
      onFocus={onFocus(index)}
      value={val[index] || ''}
      maxLength="1"
      key={index}
    />
  ));
};
const App = () => {
  const [value, setValue] = useState('');
  const onChange = useCallback(
    (value) => setValue(value),
    []
  );
  console.log('value:', value);
  return (
    <Pin
      length={5}
      value={value}
      onChange={onChange}
    />
  );
};
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>


<div id="root"></div>


All answers will shift focus to next input when you correct an already set value. The requirement is that focus should shift when a letter is inputted, not when you remove a value.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.