I am trying to create an input box for a timer/OTP styled input.
How to overwrite a current character inside input box w length 1 (OTP inputs) when it is already full?
My problem is that the input is length 1, and we move the cursor/focus to the next only after catching a single character... but once a char is filled, you have to manually backspace and delete the char and then you can refill...
currently inside input box(0) but since it is already filled, I can't overwrite it
const { useState, useRef } = React;
function App() {
const [time, setTime] = useState(null);
const [running, setRunning] = useState(false);
const [editState, setEditState] = useState(false);
const [hms, setHms] = useState(Array(6).fill(0));
const inputs = useRef([]);
const handleChange = (e, idx) => {
const {value} = e.target;
if (value.match(/^\d$/)) {
const newHms = [...hms];
newHms[idx] = value;
setHms(newHms);
if (idx < 6) {
inputs.current[idx+1].focus();
}
if (value === '') {
if (idx > 0) {
inputs.current[idx - 1].focus();
}
}
}
}
const handleKeyDown = (e, idx) => {
if (e.key === 'Backspace') {
if (hms[idx] != '') {
const newHms = [...hms];
newHms[idx] = '';
setHms(newHms);
return;
}
if (idx > 0) {
inputs.current[idx - 1].focus();
}
}
}
return (
<>
<div id='root' style={{backgroundColor: "#e3b23c", width:"400px", height:"400px"}}>
{true && <div>
{
hms.map((_, idx) => {
return (
<span>
<input
key={idx}
type='text'
maxLength="1"
value={hms[idx]}
onKeyDown={(e) => handleKeyDown(e, idx)}
onChange={(e) => handleChange(e, idx)}
ref={(el) => (inputs.current[idx] = el)}
style={{width: '40px',
height: '40px',
margin: '0 5px',
textAlign: 'center',
fontSize: '18px',
border: '1px solid #ccc',
borderRadius: '4px'}
}
></input>
{(idx == 1 || idx == 3) && <span style={{
fontWeight: 600,
fontSize: "30px"
}}>:</span>}
</span>
);
})
}
</div>}
<div>
Hours : Minutes : Seconds
</div>
</div>
</>
)
}
ReactDOM.render(<App />, document.getElementById("react"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>
Your input is already filled, and unless you select the input value and then update the value, changes will not be triggered and hence onChange
will not be triggered.
So, you have to trigger this change manually, and although onChange
is only called on change, onKeyDown
will be called on every keydown even if there is no actualy change of value.
So, the desired behaviour is achievable with the small tweak of moving all your logic from onChange
handler to onKeyDown
handler.
const handleChange = (e, idx) => {
};
const handleKeyDown = (e, idx) => {
const keyValue = e.key;
if (keyValue === "Backspace") {
if (hms[idx] != "") {
const newHms = [...hms];
newHms[idx] = "";
setHms(newHms);
return;
}
if (idx > 0) {
inputs.current[idx - 1].focus();
}
} else if (keyValue.match(/^\d$/)) {
const newHms = [...hms];
newHms[idx] = keyValue;
setHms(newHms);
if (idx < 5) {
inputs.current[idx + 1].focus();
}
}
};
Here is the codesandbox