Components
import React, { useState, useRef, useEffect } from 'react';
import { TextInput, View, Pressable, Text, NativeSyntheticEvent, TextInputKeyPressEventData } from 'react-native';
import Entypo from 'react-native-vector-icons/Entypo';
type OTPInputProps = {
length?: number;
onComplete?: (otp: string) => void;
autoFocus : true
}
export default ({ length = 4, onComplete , autoFocus}: OTPInputProps) => {
// State: Array of strings representing each digit
const [otp, setOtp] = useState<string[]>(Array(length).fill(""));
const [send, setSend] = useState<boolean>(false);
const inputs = useRef<Array<TextInput | null>>(Array(length).fill(null));
useEffect(()=>{
setOtp((prev)=>{
const next = Array(length).fill('');
for(let i = 0 ; i < Math.min(prev.length, length) ; i++) next[i] = prev[i];
return next;
});
inputs.current = Array(length).fill(null);
}, [length]);
const callOnComplete = (vals : string[])=>{
const code = vals.join("");
if(code.length === length && !vals.includes("")) onComplete?.(code);
}
const focusInput = (index : number) =>{
const ref = inputs.current[index];
ref?.focus();
}
const handleChangeText = (index : number , text : string)=>{
if(text.length > 1){
const chars = text.split("").slice(0 , length - index);
setOtp((prev)=> {
const next = [...prev];
for(let i = 0 ; i<chars.length ; i++) next[index + 1] = chars[i];
return next;
});
const nextFocusIndex = Math.min(length - 1 , index + text.length -1);
setTimeout(()=> focusInput(nextFocusIndex) , 0);
setTimeout(()=> callOnComplete(getCurrentDigits()) , 0);
return;
}
const char = text === '' ? '' : text[0];
setOtp((prev)=>{
const next = [...prev];
next[index] = char;
return next;
});
if(char !== ''){
if(index > length -1) focusInput(index + 1);
else{
callOnComplete(getCurrentDigitsWithChanges(index , char));
}
}
};
const handleKeyPress = (
index : number ,
e: NativeSyntheticEvent<TextInputKeyPressEventData>
) =>{
if(e.nativeEvent.key !== 'Backspace') return;
if(otp[index] !== ''){
setOtp((prev) =>{
const next = [...prev];
next[index] = '';
return next;
});
return;
}
if(index > 0){
setOtp((prev) =>{
const next = [...prev];
next[index - 1] = '';
return next;
});
focusInput(index -1);
}
};
const getCurrentDigits = () => otp;
const getCurrentDigitsWithChanges = (index : number , char : string) =>{
const copy = [...otp];
copy[index] = char;
return copy;
}
return (
<View className='min-h-full w-full relative'>
<View className='m-auto gap-5'>
<Text className='mx-auto text-2xl font-semibold'>Enter Your Pin</Text>
<View className='flex flex-row gap-4'>
{
Array.from({length}).map((_ , i) =>(
<TextInput
className='border border-black/10 shadow-sm size-14 rounded-[4px] text-center text-lg'
key={i}
ref={(ref) =>{inputs.current[i] = ref}}
value={otp[i]}
onChangeText={(t)=> handleChangeText(i , t)}
onKeyPress={(e) => handleKeyPress(i , e)}
keyboardType='number-pad'
returnKeyType={i === length - 1 ? 'done' : 'next'}
maxLength={1}
autoFocus={autoFocus && i === 0}
textContentType={'oneTimeCode'}
/>
))
}
{
otp.every((d) => d != '') && <Pressable
className='size-14 flex items-center justify-center border border-black/10 rounded-[4px] bg-black'
onPress={() => {
setSend(true);
setTimeout(() => setSend(false), 3000);
}}
>
<Text className='text-white'>Send</Text>
</Pressable>
}
</View>
</View>
{send && (
<View className='absolute inset-0 w-full h-full bg-white z-20'>
<View className='bg-green-200 border border-green-200 shadow-sm m-auto size-24 rounded-full flex items-center justify-center '>
<Entypo name='check' size={25} color='black' />
</View>
</View>
)}
</View>
);
};
Installation
npx solui@latest add pin-inputInstall dependencies
pnpm add nativewind react-native-reanimated@~3.17.4 react-native-safe-area-context@5.4.0 --dev tailwindcss@^3.4.17 prettier-plugin-tailwindcss@^0.5.11 react-native-vector-icons Copy the code
Copy the code from the Code tab above into components/ui/pin-input.tsx.
Update imports
Update the imports to match your project structure.
Usage
import PinInput from "@components/ui/pin-input";
export default function PinInputDemo() {
return <PinInput length={4} onComplete={()=> "pin entered successfully" } />;
}Auto complete pin input
import { AutoPin } from "@/components/ui/pin-input";
export default function PinInputDemo() {
return <AutoPin length={4} onComplete={()=> "pin entered successfully" } />;
}Props
| Prop Name | Type | Default | Description |
|---|---|---|---|
| length | number | 4 | It is length of Inputs for pin |
| onComplete | () => | '' | Method for onComplete activity |
| autoFocus | boolean | true | sets autoFocus for inputs |