Last active
May 13, 2023 21:54
-
-
Save princefishthrower/9d2a7d8403e4b2a36018b87333a837a0 to your computer and use it in GitHub Desktop.
Simple OTP TextInput for React Native
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {useIsFocused} from '@react-navigation/native'; | |
import * as React from 'react'; | |
import {useRef} from 'react'; | |
import {TextInput, View} from 'react-native'; | |
import {isIOS} from '../../helpers/utils'; | |
import {useTimeout} from '../../hooks/useTimeout'; | |
export interface IOTPInputProps { | |
otpCodeChanged: (otpCode: string) => void; | |
} | |
const NUMBER_OF_INPUTS = 6; | |
export function OTPInput(props: IOTPInputProps) { | |
const {otpCodeChanged} = props; | |
const isFocused = useIsFocused(); | |
const [values, setValues] = React.useState<string[]>([ | |
'', | |
'', | |
'', | |
'', | |
'', | |
'', | |
]); | |
const itemsRef = useRef<Array<TextInput | null>>([]); | |
const applyOTPCodeToInputs = (code: string) => { | |
// split up code and apply it to all inputs | |
const codeArray = code.split(''); | |
codeArray.forEach((char, index) => { | |
const input = itemsRef.current[index]; | |
if (input) { | |
input.setNativeProps({ | |
text: char, | |
}); | |
} | |
}); | |
// focus on last input as a cherry on top | |
const lastInput = itemsRef.current[itemsRef.current.length - 1]; | |
if (lastInput) { | |
lastInput.focus(); | |
otpCodeChanged(code); | |
} | |
}; | |
useTimeout( | |
() => { | |
// focus on the first input | |
const firstInput = itemsRef.current[0]; | |
if (firstInput) { | |
firstInput.focus(); | |
} | |
}, | |
isFocused ? 1000 : null, | |
); | |
return ( | |
<View | |
style={{flex: 1, flexDirection: 'row', justifyContent: 'space-around'}}> | |
{Array.from({length: NUMBER_OF_INPUTS}, (_, index) => ( | |
<TextInput | |
style={{ | |
paddingLeft: isIOS() ? 0 : 0, | |
paddingRight: isIOS() ? 10 : 0, | |
}} | |
ref={(el) => (itemsRef.current[index] = el)} | |
key={index} | |
keyboardType={'numeric'} | |
placeholder={'X'} | |
value={values[index]} | |
defaultValue="" | |
// first input can have a length of 6 because they paste their code into it | |
maxLength={index === 0 ? 6 : 1} | |
onChange={(event) => { | |
const {text} = event.nativeEvent; | |
// only continue one if we see a text of length 1 or 6 | |
if (text.length === 0 || text.length === 1 || text.length === 6) { | |
if (text.length === 6) { | |
applyOTPCodeToInputs(text); | |
return; | |
} | |
// going forward, only if text is not empty | |
if (text.length === 1 && index !== NUMBER_OF_INPUTS - 1) { | |
const nextInput = itemsRef.current[index + 1]; | |
if (nextInput) { | |
nextInput.focus(); | |
} | |
} | |
} | |
// determine new value | |
const newValues = [...values]; | |
newValues[index] = text; | |
// update state | |
setValues(newValues); | |
// also call callback as a flat string | |
otpCodeChanged(newValues.join('')); | |
}} | |
onKeyPress={(event) => { | |
if (event.nativeEvent.key === 'Backspace') { | |
// going backward: | |
if (index !== 0) { | |
const previousInput = itemsRef.current[index - 1]; | |
if (previousInput) { | |
previousInput.focus(); | |
return; | |
} | |
} | |
} | |
}} | |
textContentType="oneTimeCode" | |
/> | |
))} | |
</View> | |
); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment