ttdung11t2 / react-native-confirmation-code-input

A react-native component to input confirmation code for both Android and IOS
MIT License
411 stars 349 forks source link

Unable to use textContentType="oneTimeCode" #52

Open aravi365 opened 5 years ago

aravi365 commented 5 years ago

I need OTPs to be automatically filled from the keyboard using the prop : textContentType="oneTimeCode" , but when i tried only one field gets filled by the prop. How to fix this??

chevulkar commented 5 years ago

Same even i am looking of the same solution.

shubhamverma27 commented 5 years ago

+1 Need this functionality

Yasoobh1 commented 4 years ago

Android is working fine but IOS not working properly its just one field gets filled

codeapp17 commented 3 years ago

Is is still open? are there any solutions for this.. I am getting the same issue

AzranAzwer commented 2 years ago

any update on this ?

Garamani commented 1 year ago

This is the only way I could make it work after many hours of testing:

You can replace the code below with ConfirmationCodeInput.js:

import React, {Component} from 'react';
import PropTypes from 'prop-types';
import { View, TextInput, StyleSheet, Keyboard, ViewPropTypes } from 'react-native';
import _ from 'lodash';

// if ViewPropTypes is not defined fall back to View.propType (to support RN < 0.44)
const viewPropTypes = ViewPropTypes || View.propTypes;

export default class ConfirmationCodeInput extends Component {
  static propTypes = {
    codeLength: PropTypes.number,
    compareWithCode: PropTypes.string,
    inputPosition: PropTypes.string,
    size: PropTypes.number,
    space: PropTypes.number,
    className: PropTypes.string,
    cellBorderWidth: PropTypes.number,
    activeColor: PropTypes.string,
    inactiveColor: PropTypes.string,
    ignoreCase: PropTypes.bool,
    autoFocus: PropTypes.bool,
    codeInputStyle: TextInput.propTypes.style,
    containerStyle: viewPropTypes.style,
    onFulfill: PropTypes.func,
    onCodeChange: PropTypes.func,
  };

  static defaultProps = {
    codeLength: 5,
    inputPosition: 'center',
    autoFocus: true,
    size: 40,
    className: 'border-box',
    cellBorderWidth: 1,
    activeColor: 'rgba(255, 255, 255, 1)',
    inactiveColor: 'rgba(255, 255, 255, 0.2)',
    space: 8,
    compareWithCode: '',
    ignoreCase: false,
  };

  constructor(props) {
    super(props);

    this.state = {
      codeArr: new Array(this.props.codeLength).fill(''),
      currentIndex: 0
    };

    this.codeInputRefs = [];
  }

  componentDidMount() {
    const { compareWithCode, codeLength, inputPosition } = this.props;
    if (compareWithCode && compareWithCode.length !== codeLength) {
      console.error("Invalid props: compareWith length is not equal to codeLength");
    }

    if (_.indexOf(['center', 'left', 'right', 'full-width'], inputPosition) === -1) {
      console.error('Invalid input position. Must be in: center, left, right, full');
    }
  }

  clear() {
    this.setState({
      codeArr: new Array(this.props.codeLength).fill(''),
      currentIndex: 0
    });
    this._setFocus(0);
  }

  _setFocus(index) {
    this.codeInputRefs[index].focus();
  }

  _blur(index) {
    this.codeInputRefs[index].blur();
  }

  _onFocus(index) {
    const newCodeArr = _.clone(this.state.codeArr);
    const currentEmptyIndex = _.findIndex(newCodeArr, c => !c);
    if (currentEmptyIndex !== -1 && currentEmptyIndex < index) {
      return this._setFocus(currentEmptyIndex);
    }
    for (const i in newCodeArr) {
      if (i >= index) {
        newCodeArr[i] = '';
      }
    }

    this.setState({
      codeArr: newCodeArr,
      currentIndex: index
    })
  }

  _isMatchingCode(code, compareWithCode, ignoreCase = false) {
    if (ignoreCase) {
      return code.toLowerCase() === compareWithCode.toLowerCase();
    }
    return code === compareWithCode;
  }

  _getContainerStyle(size, position) {
    switch (position) {
      case 'left':
        return {
          justifyContent: 'flex-start',
          height: size
        };
      case 'center':
        return {
          justifyContent: 'center',
          height: size
        };
      case 'right':
        return {
          justifyContent: 'flex-end',
          height: size
        };
      default:
        return {
          justifyContent: 'space-between',
          height: size
        }
    }
  }

  _getInputSpaceStyle(space) {
    const { inputPosition } = this.props;
    switch (inputPosition) {
      case 'left':
        return {
          marginRight: space
        };
      case 'center':
        return {
          marginRight: space/2,
          marginLeft: space/2
        };
      case 'right':
        return {
          marginLeft: space
        };
      default:
        return {
          marginRight: 0,
          marginLeft: 0
        };
    }
  }

  _getClassStyle(className, active) {
    const { cellBorderWidth, activeColor, inactiveColor, space } = this.props;
    const classStyle = {
      ...this._getInputSpaceStyle(space),
      color: activeColor
    };

    switch (className) {
      case 'clear':
        return _.merge(classStyle, { borderWidth: 0 });
      case 'border-box':
        return _.merge(classStyle, {
          borderWidth: cellBorderWidth,
          borderColor: (active ? activeColor : inactiveColor)
        });
      case 'border-circle':
        return _.merge(classStyle, {
          borderWidth: cellBorderWidth,
          borderRadius: 50,
          borderColor: (active ? activeColor : inactiveColor)
        });
      case 'border-b':
        return _.merge(classStyle, {
          borderBottomWidth: cellBorderWidth,
          borderColor: (active ? activeColor : inactiveColor),
        });
      case 'border-b-t':
        return _.merge(classStyle, {
          borderTopWidth: cellBorderWidth,
          borderBottomWidth: cellBorderWidth,
          borderColor: (active ? activeColor : inactiveColor)
        });
      case 'border-l-r':
        return _.merge(classStyle, {
          borderLeftWidth: cellBorderWidth,
          borderRightWidth: cellBorderWidth,
          borderColor: (active ? activeColor : inactiveColor)
        });
      default:
        return className;
    }
  }

  _onKeyPress(e) {
    if (e.nativeEvent.key === 'Backspace') {
      const { currentIndex } = this.state;
      const newCodeArr = _.clone(this.state.codeArr);
      const nextIndex = currentIndex > 0 ? currentIndex - 1 : 0;
      for (const i in newCodeArr) {
        if (i >= nextIndex) {
          newCodeArr[i] = '';
        }
      }
      this.props.onCodeChange(newCodeArr.join(''))
      this._setFocus(nextIndex);
    }
  }

  _onInputCode = async (character, index) => {
    const { codeLength, onFulfill, compareWithCode, ignoreCase } = this.props;
    if (index === 0 && character.length === codeLength) {
      setTimeout(() => {

        this.setState(
          {
            codeArr: character.split(''),
          },
          () => {
            const code = character;

            if (compareWithCode) {
              const isMatching = this._isMatchingCode(code, compareWithCode, ignoreCase);
              onFulfill(isMatching, code);
              !isMatching && this.clear();
            } else {
              onFulfill(code);
            }

            Keyboard.dismiss();
          }
        )
      }, 100);
    } else if (character.length === 1){

      const { codeLength, onFulfill, compareWithCode, ignoreCase, onCodeChange } = this.props;
      let newCodeArr = _.clone(this.state.codeArr);
      newCodeArr[index] = character;

      if (index == codeLength - 1) {
        const code = newCodeArr.join('');

        if (compareWithCode) {
          const isMatching = this._isMatchingCode(code, compareWithCode, ignoreCase);
          onFulfill(isMatching, code);
          !isMatching && this.clear();
        } else {
          onFulfill(code);
        }
        this._blur(this.state.currentIndex);
      } else {
        this._setFocus(this.state.currentIndex + 1);
      }

      this.setState(prevState => {
        return {
          codeArr: newCodeArr,
          currentIndex: prevState.currentIndex + 1
        };
      }, () => { onCodeChange(newCodeArr.join('')) });
    }
  }
  render() {
    const {
      codeLength,
      codeInputStyle,
      containerStyle,
      inputPosition,
      autoFocus,
      className,
      size,
      textContentType,
      activeColor
    } = this.props;

    const initialCodeInputStyle = {
      width: size,
      height: size
    };

    const codeInputs = [];
    for (let i = 0; i < codeLength; i++) {
      const id = i;
      codeInputs.push(
        <TextInput
          key={id}
          ref={ref => (this.codeInputRefs[id] = ref)}
          style={[
            styles.codeInput, 
            initialCodeInputStyle, 
            this._getClassStyle(className, this.state.currentIndex === id),
            codeInputStyle
          ]}
          underlineColorAndroid="transparent"
          selectionColor={activeColor}
          keyboardType={'name-phone-pad'}
          returnKeyType={'done'}
          textContentType={textContentType}
          {...this.props}
          autoFocus={autoFocus && id === 0}
          onFocus={() => this._onFocus(id)}
          value={this.state.codeArr[id] ? this.state.codeArr[id].toString() : ''}
          onChangeText={text => this._onInputCode(text, id)}
          onKeyPress={(e) => this._onKeyPress(e)}
          maxLength={(i === 0 && textContentType === 'oneTimeCode' ) ? codeLength : 1}
        />
      )
    }

    return (
      <View style={[styles.container, this._getContainerStyle(size, inputPosition), containerStyle]}>
        {codeInputs}
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    flexDirection: 'row',
    marginTop: 20
  },
  codeInput: {
    backgroundColor: 'transparent',
    textAlign: 'center',
    padding: 0
  }
});

The parts that I have added: 1.Keyboard -->

import { View, TextInput, StyleSheet, Keyboard, ViewPropTypes } from 'react-native';

  1. lines 213 - 236 -->

    const { codeLength, onFulfill, compareWithCode, ignoreCase } = this.props;
    if (index === 0 && character.length === codeLength) {
      setTimeout(() => {
    
        this.setState(
          {
            codeArr: character.split(''),
          },
          () => {
            const code = character;
    
            if (compareWithCode) {
              const isMatching = this._isMatchingCode(code, compareWithCode, ignoreCase);
              onFulfill(isMatching, code);
              !isMatching && this.clear();
            } else {
              onFulfill(code);
            }
    
            Keyboard.dismiss();
          }
        )
      }, 100);
    } else if (character.length === 1){
  2. the 3 lines with this variable textContentType

Note: I haven't tested the isMatching part of the code.