0

I am currently creating a 4 digit otp (one time password) view for a react native app and i need the TextInput to progressively focus when i type a number on the previous input.

I have been able to make the style change (without focus) but the focus doesn't change with it and when i enter another digit the app crashes.

I want it such that when i type in on the first input, the focus move to the next TextInput and so on and on the last input, it focuses on submit.

How do i go about this?

Below is my code

the component

import React, { useState, useEffect } from 'react';
import { StyleSheet, Text, ScrollView, View, Alert, Image } from 'react-native';
import { SimpleLinearGradientButton } from '../../components/Buttons';
import { SimpleCard } from '../../components/Cards';

export default function(props) {
  const [otp, setOtp] = useState([]);
  const [isFocused, setIsFocused] = useState({ focused: true, index: 0 });

  const inputs = Array(4).fill(0);

 function renderInputs() {
    return inputs.map((input, index) => {
      return (
        <TextInput
          style={
            isFocused.focused && index === isFocused.index
              ? styles.inputFieldsFocused
              : styles.inputFields
          }
          key={index}
          keyboardType={'numeric'}
          onChange={focus}
        ></TextInput>
      );
    });
  }

  function focus(e) {
    setOtp(otp.concat(this.value));
    setIsFocused({ focused: true, index: isFocused.index + 1 });
    isFocused.index ? e.focus() : null;
  }

  return (
    <ScrollView contentContainerStyle={styles.content}>
      <View>
        <Image
          style={styles.image}
          source={require('../../../assets/images/verification.png')}
        />
      </View>
      <View>
        <Text>Verification</Text>
        <Text>
          Enter the 4 digit sent to your email address
        </Text>
      </View>
      <View>
        <SimpleCard>
          <>{renderInputs()}</>
        </SimpleCard>
        <SimpleLinearGradientButton
              title="Submit"
            />
          </View>
        </ScrollView>
      );
    }

The focus function was meant to focus the next input at that index but this doesn't seem to work but the style changes. How do i go about this? Thanks

codebarz
  • 327
  • 1
  • 5
  • 28

2 Answers2

1

I suggest to use single hidden TextInput, and render digits in simple View-s, based on input's value. I see no reason to have different inputs for 4 digit code.

However if you need to use different inputs, you have to control their focus states in imperative way (with refs). See focus() method. Also there are some bugs with this method (https://github.com/facebook/react-native/issues/19366)

See docs here https://facebook.github.io/react-native/docs/textinput

Rafael Hovsepyan
  • 781
  • 8
  • 14
0

tl;dr Set maxLength of textInput to 1 and on onChangeText callback change to next textInput using refs and focus()

This is a straight-forward implementation to understand easily. You can reduce the code a lot better. And I guess you're supposed to implement Backspace to clear the text and go back to previous textinput. I used onKeyPress prop to achieve that.

import React, { Component } from 'react';
import { Text, View, StyleSheet, TextInput } from 'react-native';
import Constants from 'expo-constants';

export default class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      one: '',
      two: '',
      three: '',
      oneFocus: false,
      twoFocus: false,
      threeFocus: false,
    };
  }

  componentDidMount() {
    this.refs.one.focus();
  }

    handleChangeTextOne = (text) => {
      this.setState({ one: text }, () => { if (this.state.one) this.refs.two.focus(); });
    }
  handleChangeTextTwo = (text) => {
    this.setState({ two: text }, () => { if (this.state.two) this.refs.three.focus(); });
  }
  handleChangeTextThree = (text) => {
    this.setState({ three: text });
  }

    backspace = (id) => {
      if (id === 'two') {
        if (this.state.two) { this.setState({ two: '' }); } else if (this.state.one) { this.setState({ one: '' }); this.refs.one.focus(); }
      } else if (id === 'three') {
        if (this.state.three) { this.setState({ three: '' }); } else if (this.state.two) { this.setState({ two: '' }); this.refs.two.focus(); }
      }
    }

    render() {
      const { oneFocus, twoFocus, threeFocus } = this.state;
      const oneStyle = {
        borderBottomColor: oneFocus ? 'red' : 'black',
        borderBottomWidth: oneFocus ? 2 : 1,
      };
      const twoStyle = {
        borderBottomColor: twoFocus ? 'red' : 'black',
        borderBottomWidth: twoFocus ? 2 : 1,
      };
      const threeStyle = {
        borderBottomColor: threeFocus ? 'red' : 'black',
        borderBottomWidth: threeFocus ? 2 : 1,
      };
      return (
        <View style={styles.container}>
          <View style={styles.inputcontainer}>
            <TextInput
              ref='one'
              style={[styles.textInput, { ...oneStyle }]}
              autoCorrect={false}
              autoCapitalize='none'
              keyboardType='number-pad'
              caretHidden
              onFocus={() => this.setState({ oneFocus: true })}
              onBlur={() => this.setState({ oneFocus: false })}
              maxLength={1}
              onChangeText={(text) => { this.handleChangeTextOne(text); }}
              value={this.state.one}
            />
            <TextInput
              ref='two'
              onKeyPress={({ nativeEvent }) => (
                nativeEvent.key === 'Backspace' ? this.backspace('two') : null
              )}
              style={[styles.textInput, { ...twoStyle }]}
              autoCorrect={false}
              autoCapitalize='none'
              maxLength={1}
              onFocus={() => this.setState({ twoFocus: true })}
              onBlur={() => this.setState({ twoFocus: false })}
              caretHidden
              keyboardType='number-pad'
              onChangeText={(text) => { this.handleChangeTextTwo(text); }}
              value={this.state.two}
            />
            <TextInput
              ref='three'
              onKeyPress={({ nativeEvent }) => (
                nativeEvent.key === 'Backspace' ? this.backspace('three') : null
              )}
              style={[styles.textInput, { ...threeStyle }]}
              autoCorrect={false}
              autoCapitalize='none'
              onFocus={() => this.setState({ threeFocus: true })}
              onBlur={() => this.setState({ threeFocus: false })}
              maxLength={1}
              caretHidden
              keyboardType='number-pad'
              onChangeText={(text) => { this.handleChangeTextThree(text); }}
              value={this.state.four}
            />
          </View>
        </View>
      );
    }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
  }, 
  inputcontainer: {
    height: '5%',
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'flex-start',
    paddingHorizontal: '20%',
    marginBottom: '2%',
  }, 
  textInput: {
    fontSize: 22,
    textAlign: 'center',
    paddingVertical: 0,
    paddingHorizontal: 0,
    width: '12%',
  },
});

Run this in snack: https://snack.expo.io/@legowtham/otp-textinput-example-react-native

Gowtham
  • 408
  • 5
  • 16