Reputation: 10294
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>
)
}
Upvotes: 9
Views: 34673
Reputation: 39340
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.
Upvotes: -2
Reputation: 21
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.
Upvotes: 1
Reputation: 22935
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>
)
}
Upvotes: 18
Reputation: 612
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>
)
}
Upvotes: 13
Reputation: 3274
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();
Upvotes: 0