UNPKG

react-native-otp-input

Version:

A tiny JS library which provides an elegant UI for user to input one time passcode (OTP). It features robust checks to handle edge cases for the highly volatile user inputs. We provide default UI but you can customize the appearance as you like.

192 lines (172 loc) 6.01 kB
import React, { Component } from 'react' import { View, TextInput, TouchableWithoutFeedback, Clipboard } from 'react-native' import PropTypes from 'prop-types' import styles from './styles' export default class OTPInputView extends Component { static propTypes = { pinCount: PropTypes.number, codeInputFieldStyle: PropTypes.object, codeInputHighlightStyle: PropTypes.object, onCodeFilled: PropTypes.func, code: PropTypes.string, } static defaultProps = { pinCount: 6, codeInputFieldStyle: null, codeInputHighlightStyle: null, onCodeFilled: null, code: "", } state = { digits: [], selectedIndex: 0, } fields = [] componentDidMount() { const focusIndex = this.props.code.length ? this.props.code.length - 1 : 0 this.setState({ digits: this.props.code.split(""), }, () => { if (focusIndex === 0) { this._focusField(focusIndex) } }) this.checkPinCodeFromClipBoard() this._timer = setInterval(() => { this.checkPinCodeFromClipBoard() }, 400) } componentWillUnmount() { if (this._timer) { clearInterval(this._timer) } } checkPinCodeFromClipBoard = () => { Clipboard.getString().then(code => { if (this._hasCheckedCode && code.length === this.props.pinCount && (this._code !== code)) { this.setState({ digits: code.split(""), }, () => { this._blurAllFields() }) } this._code = code this._hasCheckedCode = true }).catch(e => { }) } componentWillReceiveProps(nextProps) { if (nextProps.code !== this.state.digits) { this.setState({ digits: nextProps.code.split(""), }, () => { this._focusField(0) }) } } render() { return ( <View style={this.props.style} > <TouchableWithoutFeedback style={{width: '100%', height: '100%'}} onPress={ () => { let filledPinCount = this.state.digits.filter((digit) => { return !!digit }).length this._focusField(Math.min(filledPinCount, this.props.pinCount - 1)) }} > <View style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', width: '100%', height: '100%' }} > {this._renderTextFields()} </View> </TouchableWithoutFeedback> </View> ); } _renderOneInputField = (index) => { const {codeInputFieldStyle, codeInputHighlightStyle} = this.props const {defaultTextFieldStyle} = styles return ( <View pointerEvents="none" key={index + "view"}> <TextInput underlineColorAndroid='rgba(0,0,0,0)' style={this.state.selectedIndex === index ? [defaultTextFieldStyle, codeInputFieldStyle, codeInputHighlightStyle] : [defaultTextFieldStyle, codeInputFieldStyle]} ref={ref => { this.fields[index] = ref }} onChangeText={text => { this._onChangeText(index, text) }} onKeyPress={({ nativeEvent: { key } }) => { this._onKeyPress(index, key) }} value={this.state.digits[index]} keyboardType="number-pad" textContentType="oneTimeCode" key={index} selectionColor="#00000000" /> </View> ) } _renderTextFields = () => { let array = new Array() for (i = 0; i<this.props.pinCount; i++) { array[i] = i } return array.map(this._renderOneInputField) } _onChangeText = (index, text) => { const {onCodeFilled} = this.props let newdigits = this.state.digits.slice() const oldTextLength = newdigits[index] ? newdigits[index].length : 0 const newTextLength = text.length if (newTextLength - oldTextLength === this.props.pinCount) { //User copy pasted text in. newdigits = text.split("").slice(oldTextLength, newTextLength) this.setState( {digits: newdigits }) } else { if (text.length === 0) { if (newdigits.length > 0) { newdigits = newdigits.slice(0, newdigits.length-1) } } else { text.split("").forEach((value) => { newdigits[index] = value index += 1 }) index -= 1 } this.setState({ digits: newdigits }) } let result = newdigits.join("") if (result.length >= this.props.pinCount) { onCodeFilled && onCodeFilled(result) this._focusField(this.props.pinCount - 1) this._blurAllFields() } else { if (text.length > 0 && index < this.props.pinCount - 1) { this._focusField(index + 1) } } } _onKeyPress = (index, key) => { if(key === 'Backspace') { if (!this.state.digits[index] && index > 0) { this._onChangeText(index - 1, '') this._focusField(index - 1) } } } _focusField = (index) => { this.fields[index].focus() this.setState({ selectedIndex: index }) } _blurAllFields = () => { for (field of this.fields) { field.blur() } this.setState({ selectedIndex: -1, }) } }