---
title: Make a snazzy one-time code input in React Native
teaser: 'Let''s take a look at making a nice one-time code input in React Native and
  how we can make the best possible experience for our mobile application users.

  '
tags: react native,user experience,mobile,design
author:
- John Schoeman
- Devin Jameson
published_on: 2021-01-14
---

If you've used a mobile app in the past few years, odds are you've used your
phone number to sign into an account. It's a familiar process: enter your
number, receive an SMS with a one-time code, and enter that code to sign in.

We can make this experience a little nicer for users by rendering a code input
that is tailored to accept and display four to six digits.

![Four narrow inputs placed in a row. Each input accepts one number. The numpad keyboard is also displayed.](https://images.thoughtbot.com/blog-vellum-image-uploads/rHGkZ7L6Sp6HBn1RCxfY_code_input.gif)

Unfortunately, we can't create this UI with a single `TextInput`. Our strategy will be to capture the user's input
with a hidden `TextInput` and display the code with separate components.

Let's start with the skeleton of our component. We'll ignore the usual imports from React
and React Native as well as the export of the component for the purposes of this post.

```typescript
const CODE_LENGTH = 4;

const CodeInput = () => {
  return <SafeAreaView style={style.container}></SafeAreaView>;
};

const style = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
  },
});
```

Now we can add a hidden `TextInput` to capture the code from the user as well as some state to
hold the code.

```typescript
const CodeInput = () => {
  const [code, setCode] = useState('');

  return (
    <SafeAreaView style={style.container}>
      <TextInput
        value={code}
        onChangeText={setCode}
        keyboardType="number-pad"
        returnKeyType="done"
        textContentType="oneTimeCode"
        maxLength={CODE_LENGTH}
        style={style.hiddenCodeInput}
      />
    </SafeAreaView>
  );
};

const style = StyleSheet.create({

  ...

  hiddenCodeInput: {
    position: 'absolute',
    height: 0,
    width: 0,
    opacity: 0,
  },
});
```

Next we need to display the code entered by the user and focus the hidden `TextInput` when the code is pressed.

To display the code digits, we can [map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map) over an array of the code length. For each array element, we'll key into the `code` string with the index. If there is no character at the current index (if the code is
too short), we'll render an empty space instead.

```typescript
const CODE_LENGTH = 4;

const CodeInput = () => {
  const codeDigitsArray = new Array(CODE_LENGTH).fill(0);

  const toDigitInput = (_value: number, idx: number) => {
    const emptyInputChar = ' ';
    const digit = code[idx] || emptyInputChar;

    return (
      <View key={idx}>
        <Text>{digit}</Text>
      </View>
    );
  };

  return (
    <SafeAreaView style={style.container}>
      <Pressable style={style.inputsContainer}>
        {codeDigitsArray.map(toDigitInput)}
      </Pressable>
      <TextInput
        value={code}
        onChangeText={setCode}
        keyboardType="number-pad"
        returnKeyType="done"
        textContentType="oneTimeCode"
        maxLength={CODE_LENGTH}
        style={style.hiddenCodeInput}
      />
    </SafeAreaView>
  );
};

...
```

Next, we want to focus the hidden `TextInput` when the user presses the code. To
imperatively focus the `TextInput`, we will need to use a [ref](https://reactjs.org/docs/refs-and-the-dom.html).

```typescript
const ref = useRef<TextInput>(null);

const handleOnPress = () => {
  ref?.current?.focus();
};

...

<SafeAreaView style={style.container}>
  <Pressable style={style.inputsContainer} onPress={handleOnPress}>
    {codeDigitsArray.map(toDigitInput)}
  </Pressable>
  <TextInput
    ref={ref}
    value={code}
    onChangeText={setCode}
    keyboardType="number-pad"
    returnKeyType="done"
    textContentType="oneTimeCode"
    maxLength={CODE_LENGTH}
    style={style.hiddenCodeInput}
  />
</SafeAreaView>

...
```

Now our code input is fully functional!

Next, we'll style the code input as a whole and the individual digits.
Then we can add some conditional styling to highlight the current digit.

```typescript
const toDigitInput = (_value: number, idx: number) => {
  const emptyInputChar = ' ';
  const digit = code[idx] || emptyInputChar;

  return (
    <View key={idx} style={style.inputsContainer}>
      <Text style={style.inputText}>{digit}</Text>
    </View>
  );
};

...

<SafeAreaView style={style.container}>
  <Pressable style={style.inputsContainer} onPress={handleOnPress}>
    {codeDigitsArray.map(toDigitInput)}
  </Pressable>
  <TextInput
    ref={ref}
    value={code}
    onChangeText={setCode}
    keyboardType="number-pad"
    returnKeyType="done"
    textContentType="oneTimeCode"
    maxLength={CODE_LENGTH}
    style={style.hiddenCodeInput}
  />
</SafeAreaView>

...

const style = StyleSheet.create({

  ...

  inputsContainer: {
    width: '60%',
    flexDirection: 'row',
    justifyContent: 'space-between',
  },
  inputContainer: {
    borderColor: '#cccccc',
    borderWidth: 2,
    borderRadius: 4,
    padding: 12,
  },
  inputText: {
    fontSize: 24,
    fontFamily: 'Menlo-Regular',
  },

  ...

});
```

Time for some conditional styling.

First, we'll introduce some state to hold the focus state,
which we will toggle when the user focuses and blurs the input.
Finally, we'll apply some styling to highlight the current digit.

The following must be true for the current digit to be highlighted:

- the code input as a whole is focused by the user

`containerIsFocused === true`

AND

- the index of the digit is equal to the current length of the inputted code

`const isCurrentDigit = idx === code.length;`

OR

- the code is full and it's the last digit

```
const isLastDigit = idx === CODE_LENGTH - 1;
const isCodeFull = code.length === CODE_LENGTH;
```

Applied to the component, the final styling logic looks like this:

```typescript
const CODE_LENGTH = 4;

const CodeInput = () => {

  ...

  const [containerIsFocused, setContainerIsFocused] = useState(false);

  const handleOnPress = () => {
    setContainerIsFocused(true);
    ref?.current?.focus();
  };

  const handleOnBlur = () => {
    setContainerIsFocused(false);
  };

  ...

  const toDigitInput = (_value: number, idx: number) => {
    const emptyInputChar = ' ';
    const digit = code[idx] || emptyInputChar;

    const isCurrentDigit = idx === code.length;
    const isLastDigit = idx === CODE_LENGTH - 1;
    const isCodeFull = code.length === CODE_LENGTH;

    const isFocused = isCurrentDigit || (isLastDigit && isCodeFull);

    const containerStyle =
      containerIsFocused && isFocused
        ? {...style.inputContainer, ...style.inputContainerFocused}
        : style.inputContainer;

    return (
      <View key={idx} style={containerStyle}>
        <Text style={style.inputText}>{digit}</Text>
      </View>
    );
  };

  return (

      ...

      <TextInput
        ref={ref}
        value={code}
        onChangeText={setCode}
        onSubmitEditing={handleOnBlur}
        keyboardType="number-pad"
        returnKeyType="done"
        textContentType="oneTimeCode"
        maxLength={CODE_LENGTH}
        style={style.hiddenCodeInput}
      />

      ...

  )

  ...

};

```

That's it! Here's the [GitHub repo](https://github.com/thoughtbot/react-native-code-input-example) for the code.

![Four narrow inputs placed in a row. Each input accepts one number. The numpad keyboard is also displayed.](https://images.thoughtbot.com/blog-vellum-image-uploads/rHGkZ7L6Sp6HBn1RCxfY_code_input.gif)

## Speed up development

Get your app to market faster with thoughtbot's proven and reliable approach to mobile design and development. [Learn more](https://thoughtbot.com/blog/make-a-snazzy-code-input-in-react-native) about how thoughtbot can deliver better and faster outcomes for your mobile app.
