medium+190 XP
Build an OTP input
Build a 4-digit one-time-passcode input: four separate single-character boxes that read as one value.
Typing a digit in a box should move focus to the next box automatically. Pressing Backspace in a box that is already empty should move focus back to the previous box instead of doing nothing.
Requirements
- Four inputs, `data-testid="otp-0"` through `data-testid="otp-3"`, each holding at most one character.
- A `data-testid="otp-value"` element shows the concatenation of all four boxes as plain text.
- Typing a character into a box moves focus to the next box (if there is one).
- Pressing Backspace in an empty box moves focus to the previous box (if there is one) — Backspace in a non-empty box behaves normally otherwise.
- Typing in the last box does not error or try to move focus past it.
Your workspace
import { useRef, useState } from "react"; export default function App() { const [values, setValues] = useState(["", "", "", ""]); const inputRefs = [useRef(null), useRef(null), useRef(null), useRef(null)]; function handleChange(index, e) { const char = e.target.value.slice(-1); setValues((current) => { const next = [...current]; next[index] = char; return next; }); if (char && index < 3) { inputRefs[index + 1].current?.focus(); } } function handleKeyDown(index, e) { // TODO: pressing Backspace while this box is already empty should // move focus to the previous box (index - 1), if there is one. } return ( <div style={{ fontFamily: "sans-serif", padding: 16 }}> <div style={{ display: "flex", gap: 8 }}> {values.map((value, index) => ( <input key={index} data-testid={`otp-${index}`} ref={inputRefs[index]} value={value} maxLength={1} onChange={(e) => handleChange(index, e)} onKeyDown={(e) => handleKeyDown(index, e)} style={{ width: 32, height: 40, textAlign: "center", fontSize: 20 }} /> ))} </div> <p> Value: <span data-testid="otp-value">{values.join("")}</span> </p> </div> ); }
Ready to check it?
5 tests run against your rendered component, right here in your browser. Sign in to claim the XP when you pass.
Stuck? The react course covers the ideas this problem is built on.