5

I want to create an OTP enter screen with 6 TextInput. I want the TextInput to autofocus on to the next one as I type the value of the previous TextInput.enter image description here

I followed the solution given in the following question.

But I am getting an exception TypeError: null is not an object(evaluating 'textInputToFocus.current.focus') so basically my variable textInputToFocus is null in the below code and I am not sure why?

import React, { Component } from 'react';
import {
  StyleSheet,
  View,
  Text,
} from 'react-native';
import { PasscodeTextInput } from '../Common/PasscodeTextInput';
import Button from 'react-native-button';

type Props = {}

export default class EnterOTP extends React.Component {

  constructor(props) {
      super(props)
      this.state = {
          passcode1: "",
          passcode2: "",
          passcode3: "",
          passcode4: "",
          passcode5: "",
          passcode6: "",
      }
      this.passcode1 = React.createRef()
      this.passcode2 = React.createRef()
      this.passcode3 = React.createRef()
      this.passcode4 = React.createRef()
      this.passcode5 = React.createRef()
      this.passcode6 = React.createRef()
      this.inputNumber = this.inputNumber.bind(this); 

  }

  onVerify = () => {

  }

  inputNumber(value, flag) {
    const completeFlag = `passcode${flag}`
    console.log(completeFlag);
    console.log(value)
    this.setState({[completeFlag]: value})
    console.log(this.state);
    flag = flag + 1
    if (flag < 7 && value) {
        const nextFlag = `passcode${flag}`
        console.log(nextFlag);
        const textInputToFocus = this[nextFlag]
        console.log(textInputToFocus)
        textInputToFocus.current.focus()
    }
}
  render() {
    return (
    <View style={styles.container}>
        <View style={styles.leftContainer}>
            <Text style = {styles.firstText}>SMS Verification</Text>
            <Text style = {styles.secondText}>We have sent an SMS with a verification code to +91 7777777777. Please enter it below.</Text>
        </View>
        <View style={[styles.passcodeContainer]}>
          <PasscodeTextInput
            autoFocus={true}
            ref={this.passcode1}
            onChangeText={number => this.inputNumber(number, 1)} />
          <PasscodeTextInput
            ref={this.passcode2}
            onChangeText={number => this.inputNumber(number, 2)} />
          <PasscodeTextInput
            ref={this.passcode3}
            onChangeText={number => this.inputNumber(number, 3)}/>
          <PasscodeTextInput
            ref={this.passcode4}
            onChangeText={number => this.inputNumber(number, 4)} />
          <PasscodeTextInput
            ref={this.passcode5} 
            onChangeText={number => this.inputNumber(number, 5)}/>
          <PasscodeTextInput
            ref={this.passcode6} 
            onChangeText={number => this.inputNumber(number, 6)}/>
        </View>
        <View styles={[styles.centerEverything]}>
            <Button
            style={{ fontSize: 20, color: 'white' }}
            containerStyle={styles.verifyButton}
            onPress={() => this.onVerify()}
            >
            VERIFY
            </Button>
        </View>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  centerEverything: {
    justifyContent: 'center',
    alignItems: 'center',
    flexDirection: 'row'
    //backgroundColor: 'red'
  },
  container: {
    flex: 1,
    backgroundColor: 'white',
  },
  leftContainer: {
    justifyContent: 'flex-start',
    marginLeft: 20,
    marginRight: 20,
    marginTop: 50
  },
  passcodeContainer: {
    flexDirection: 'row',
    justifyContent: 'center',
    alignItems: 'center',
  },
  firstText: {
      color:"#758D9E",
      marginTop: 12,
      fontSize: 30,
      fontWeight: 'bold',
      textAlign: 'left',
      alignItems: 'flex-start',
      marginLeft: 5,
      marginRight: 5
    },
    secondText: {
      color:"#758D9E",
      marginTop: 18,
      fontSize: 14,
      marginLeft: 10,
      marginRight: 10
    },
    verifyButton: {
      //flex: 0.1,
      justifyContent: 'center',
      alignItems: 'center',
      marginTop:30,
      backgroundColor:'#F64658',
      borderRadius:100,
      borderWidth: 1,
      borderColor: '#fff',
      overflow: 'hidden',
      height: 40,
      //width: 300,
      margin: 30
    },


});

EDIT: My PasscodeTextInput code after React.forwardRef

import React from 'react';
import {
  View,
  Text,
  TextInput,
} from 'react-native';

const PasscodeTextInput = React.forwardRef(({ ref, autoFocus, onSubmitEditing, onChangeText, value}) => {

  const { inputStyle, underlineStyle } = styles;

  return(
    <View>
      <TextInput
        ref={ref}
        autoFocus={autoFocus}
        onSubmitEditing={onSubmitEditing}
        style={[inputStyle]}
        maxLength={1}
        keyboardType="numeric"
        placeholderTextColor="#212121"
        //secureTextEntry={true}
        onChangeText={onChangeText}
        value={value}
      />
      <View style={underlineStyle} />
    </View>
  );
})

const styles = {
  inputStyle: {
    height: 80,
    width: 30,
    fontSize: 50,
    color: '#212121',
    fontSize: 20,
    padding: 5,
    margin: 5,
    marginBottom: 0
  },
  underlineStyle: {
    width: 30,
    height: 4,
    backgroundColor: '#202020',
    marginLeft: 0
  }
}

export { PasscodeTextInput };
Exception
  • 2,273
  • 1
  • 24
  • 42

2 Answers2

2

tl. dr. Change the declaration from
const PasscodeTextInput = ({ ref, autoFocus, onSubmitEditing, onChangeText, value}) => {
To:
const PasscodeTextInput = React.forwardRef(({autoFocus, onSubmitEditing, onChangeText, value}, ref) => {
And don't forget to close the new parenthesis

Explanation:
ref is not a normal prop which is handled down to your component. When using the ref prop, react internals will handle that prop, try to get a reference to the actual instance of your component and set it there, and the prop never arrives to your actual component code.
Since your PasswordTextInput component is defined as a function, there is no instace of it so you get null.

What you want to do is forward the ref down to the TextInput component. So you explicitly say to react to please give you the ref prop so you can handle it however you think it suits you. Which in this case is forwarding it to the TextInput component.

For more info see https://reactjs.org/docs/forwarding-refs.html

Mark E
  • 3,403
  • 2
  • 22
  • 36
  • Made the changes, check the edited PasscodeTextInput but I am still getting the same error. – Exception Nov 11 '19 at 06:40
  • @Exception My bad, as stated in the docs linked at the end, the ref is passed as a second argument to the answer when using forwardRef. I updated the answer changing the `PasscodeTextInput` definition to receive `ref` as a second argument. – Mark E Nov 11 '19 at 08:06
0

May be you can try using custom attributes in JSX.

attribute operator:

let autofocus= {'attribute ': 'value'}

to render:

   {...autofocus}

for example;

Stateful component:

 ......
    <Input
     isAutofocus={true}
     tabindex="1"
    ........
    />

Stateless component:

import React from "react";
const Input = ({isAutofocus,tabindex  .......}) => {
  let autofocus = isAutofocus ? {'autoFocus' : " "}  : ""
  return (
    <div className="form-group">
      <label htmlFor={name}>{label}</label>
      <input
        {...autofocus}
       tabIndex={tabindex}
        ............
      />
    </div>
   )
}
export default Input;
YongSS
  • 1
  • take note : for ( let autofocus = isAutofocus ? {'autoFocus' : " "} : "" ) if using autoFocus, this will conflict with html attributes – YongSS Jul 12 '20 at 17:54