react-native-otp-ui-kit
Version:
react-native-otp-ui-kit is a simple and highly customizable React Native component for entering OTP (One-Time Password) on iOS, Android, and Web. It provides an intuitive and user-friendly interface for inputting one-time passwords in your React Native ap
113 lines (94 loc) • 3.38 kB
text/typescript
import { useEffect, useRef, useState } from 'react';
import { TextInput } from 'react-native';
import { OtpInputProps } from './OTPInputProps';
interface UseOtpInputProps extends OtpInputProps {
length: number;
}
export const useOtpInput = ({
length,
onCodeChanged,
onOTPFilled,
disabled = false,
autoFocus = true,
}: UseOtpInputProps) => {
const [otp, setOtp] = useState<string[]>(Array(length).fill(''));
const [focusedIndex, setFocusedIndex] = useState<number | null>(null);
const inputRefs = useRef<Array<TextInput | null>>([]);
// Automatically focus the first input on mount if autofocus is true
useEffect(() => {
if (autoFocus) {
inputRefs.current[0]?.focus();
}
}, [autoFocus]);
const handleTextChange = (index: number, text: string) => {
if (isNaN(Number(text)) || disabled) return;
const newOtp = [...otp];
newOtp[index] = text;
setOtp(newOtp);
const combinedOtp = newOtp.join('');
if (onCodeChanged) {
onCodeChanged(combinedOtp);
}
if (combinedOtp.length === length && onOTPFilled) {
setTimeout(() => onOTPFilled(combinedOtp), 100);
}
// Move to next input if there's text and it's not the last field
if (text && index < otp.length - 1) {
let nextIndex = index + 1;
while (nextIndex < otp.length && newOtp[nextIndex]) {
nextIndex++;
}
inputRefs.current[nextIndex]?.focus();
}
};
const handleFocus = (index: number) => {
// Get the index of the first empty OTP field
const firstEmptyIndex = otp.indexOf('');
let targetIndex = index;
// Check if the user is moving backward and focus the previous empty field
if (otp[index]) {
targetIndex = index;
} else if (firstEmptyIndex !== -1) {
// If there is an empty field, focus the first empty field
targetIndex = firstEmptyIndex;
} else {
// If no empty field, maintain focus on the current index
targetIndex = index;
}
setFocusedIndex(targetIndex); // Update the focused index
inputRefs.current[targetIndex]?.focus(); // Focus the input field
};
const handleBlur = () => {
setFocusedIndex(null);
};
const handleKeyDown = (index: number, event: { nativeEvent: { key: string } }) => {
if (event.nativeEvent.key === 'Backspace' && index > 0) {
const newOtp = [...otp];
if (!newOtp[index]) {
// Move to the previous empty field if the current is empty
const prevIndex = otp.slice(0, index).lastIndexOf('');
inputRefs.current[prevIndex !== -1 ? prevIndex : index - 1]?.focus();
} else {
// Clear the current OTP field
newOtp[index] = '';
setOtp(newOtp);
}
}
};
const clear = () => {
setOtp(Array(length).fill(''));
inputRefs.current[0]?.focus();
};
return {
otp,
focusedIndex,
inputRefs,
actions: {
handleTextChange,
handleFocus,
handleBlur,
handleKeyDown,
clear,
},
};
};