šŸŽ Checkout my Learn React by Making a Game course and get 1 month Free on Skillshare!

Making a React pincode field component

Recently I needed to add something similar to a pincode in a React application. Something like when you enter your pin at the ATM, only that this one could accept also letters, not just numbers.

And of course, I ended up reinventing the wheel, instead of just using a built-in component šŸ¤¦ā€ā™‚ļø.

So, in today's article, I will share my approach.

Defining the React pincode component

The requirements:

  • we can pass it a property to set the characters length of the pin
  • it will receive a callback function that will be called when the pin was fully typed
  • if we don't start on the first digit, we are forced to the first one
  • it will auto-focus the next digit after a digit was typed

The end result will work as in this video:

React components Structure

We will have two React components:

1. the actual <Pincode> component that will receive the callback function for when the pin was fully typed and the total length of the pin:

const PinCode = ({ length, onPinEntered }) => { }

const App = ()=> {
  return (
    <div className="App">
      <PinCode 
            length={5} 
            onPinEntered={(pin) => alert("Pin : " + pin)} />
    </div>
  );
}

2. the <Digit> component will also have a callback function for when a digit is typed, a ref object, and a num index value that we will use to move and focus the next digit:

const Digit = forwardRef(({ num, onDigitAdded }, ref) => {})

Building the React pincode component

The first step will be just to render the digits inputs in a loop, based on the wanted length of the final pin:

const PinCode = ({ length, onPinEntered }) => {
  let [pin, setPin] = useState("");
  const onDigitAdded = (d) => {
    setPin(pin + d.value);
    focusNextDigit(d);
  };

  return (
    <div onClick={onClickHandler}>
      {[...Array(length).keys()].map((i) => (
        <Digit
          key={i}
          num={i}
          onDigitAdded={onDigitAdded}
        />
      ))}
    </div>
  );
};

At this point the component will look as follows:
React pincode filed unstyled

Using the onDigitAdded() function we add digits to the state variable pin.

When the pin var has the same size as the lenght property of the PinCode component then it means we have fully typed the pin and we will pass it to the callback function.

const PinCode = ({ length, onPinEntered }) => {
  // ...
  useEffect(() => {
    if (pin.length === length) {
      onPinEntered(pin);
    }
  }, [pin, onPinEntered, length]);
}

The overall structure of the Digit component is pretty simple:

const Digit = forwardRef(({ num, onDigitAdded }) => {
  const keyUpHandler = (e) => onDigitAdded(e.target);
  return (
    <input
      onKeyUp={keyUpHandler}
      type="password"
      maxLength="1"
      data-index={num}
    />
  );
});

Working with multiple React refs to manage the focus of the PinCode Component

You may wonder why the Digit component is wrapped in the forwardRef() hook!?

Well, initially my first attempt was to use the querySelectorAll() function to manage the focus of the multiple-digit inputs.

However, realized that it's not a good idea to use querySelectorAll() in React as it will mess up the Virtual DOM.

Therefore, the next try was to add refs to each Digit component:

const PinCode = ({ length, onPinEntered }) => {
  const digits = useRef([]);
  // ...
  return (
    <div onClick={onClickHandler}>
      {[...Array(length).keys()].map((i) => (
        <Digit
          ref={(d) => digits.current.push(d)}
    // ...
  );

And now we can implement the focusNextDigit() function in the PinCode component:

const focusNextDigit = (d) => {
    const index = parseInt(d.dataset.index);
    const nextDigit = digits.current[index + 1];
    nextDigit?.focus();
}; 

And also, if the user does not start on the first digit, we will auto-move it there:

const onClickHandler = () => {
    if (pin !== "") {
      setPin("");
      digits.current.forEach((d) => {
        if (d) d.value = "";
      });
    }
    digits.current[0].focus();
};

Wrote more here about how to use an array of components with the useRef() hook.

At this point, all the functionality should work as expected.

Our example can be improved by adding support for deleting characters or for pasting codes from the clipboard. In order to be able to paste codes in this component you can use this post as a starting point, and I've also written an intro on how to add copy-paste support for React components.

Styling the PinCode React Component

The final step will be to add some CSS. Fortunately, this step was pretty simple:

body {
  font-family: sans-serif;
  text-align: center;
  background-color: orangered;
}

input {
  width: 50px;
  height: 50px;
  margin: 0.5rem;
  padding: 0.5rem;
  border: 2px solid white;
  text-align: center;
  font-size: 5rem;
  color: white;
  background-color: orangered;
}

After this, we will the final output:
React pincode field component

Full code and live example

You can checkout on my Github the full code and see the live example here.

šŸ“– 50 Javascript, React and NextJs Projects

Learn by doing with this FREE ebook! Not sure what to build? Dive in with 50 projects with project briefs and wireframes! Choose from 8 project categories and get started right away.

šŸ“– 50 Javascript, React and NextJs Projects

Learn by doing with this FREE ebook! Not sure what to build? Dive in with 50 projects with project briefs and wireframes! Choose from 8 project categories and get started right away.


Leave a Reply

Your email address will not be published. Required fields are marked *