From d3aa273909e8fc4c5f8ec153af87c57b17233874 Mon Sep 17 00:00:00 2001 From: Ahmed Saeed <115148623+ahmed-s-fatahallah@users.noreply.github.com> Date: Sun, 12 May 2024 21:13:32 +0300 Subject: [PATCH 1/3] fix: Values always added from first input even when another input is selected or focused --- src/index.tsx | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/src/index.tsx b/src/index.tsx index 3afd218..4199286 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useRef } from 'react'; type AllowedInputTypes = 'password' | 'text' | 'number' | 'tel'; @@ -70,7 +70,8 @@ const OTPInput = ({ const [activeInput, setActiveInput] = React.useState(0); const inputRefs = React.useRef>([]); - const getOTPValue = () => (value ? value.toString().split('') : []); + // Save Otp value in a ref to persist it between rerenders to update the value in the current focused input + const otpValueRef = React.useRef(value ? value.toString().split('') : Array(numInputs)); const isInputNum = inputType === 'number' || inputType === 'tel'; @@ -84,6 +85,11 @@ const OTPInput = ({ } }, [shouldAutoFocus]); + // On each Rerender check if the value is an empty string we reset the otpValueRef value + if (value.trim() === '') { + otpValueRef.current = Array(numInputs); + } + const getPlaceholderValue = () => { if (typeof placeholder === 'string') { if (placeholder.length === numInputs) { @@ -114,7 +120,6 @@ const OTPInput = ({ const handleInputChange = (event: React.ChangeEvent) => { const { nativeEvent } = event; const value = event.target.value; - if (!isInputValueValid(value)) { // Pasting from the native autofill suggestion on a mobile device can pass // the pasted string as one long input to one of the cells. This ensures @@ -152,7 +157,8 @@ const OTPInput = ({ }; const handleKeyDown = (event: React.KeyboardEvent) => { - const otp = getOTPValue(); + const currentOtpValue = otpValueRef.current; + if ([event.code, event.key].includes('Backspace')) { event.preventDefault(); changeCodeAtFocus(''); @@ -169,7 +175,7 @@ const OTPInput = ({ } // React does not trigger onChange when the same value is entered // again. So we need to focus the next input manually in this case. - else if (event.key === otp[activeInput]) { + else if (event.key === currentOtpValue[activeInput]) { event.preventDefault(); focusInput(activeInput + 1); } else if ( @@ -193,9 +199,10 @@ const OTPInput = ({ }; const changeCodeAtFocus = (value: string) => { - const otp = getOTPValue(); - otp[activeInput] = value[0]; - handleOTPChange(otp); + const currentOtpValue = otpValueRef.current; + currentOtpValue[activeInput] = value[0]; + + handleOTPChange(currentOtpValue); }; const handleOTPChange = (otp: Array) => { @@ -206,14 +213,11 @@ const OTPInput = ({ const handlePaste = (event: React.ClipboardEvent) => { event.preventDefault(); - const otp = getOTPValue(); + const currentOtpValue = otpValueRef.current; let nextActiveInput = activeInput; // Get pastedData in an array of max size (num of inputs - current position) - const pastedData = event.clipboardData - .getData('text/plain') - .slice(0, numInputs - activeInput) - .split(''); + const pastedData = event.clipboardData.getData('text/plain').slice(0, numInputs).split(''); // Prevent pasting if the clipboard data contains non-numeric values for number inputs if (isInputNum && pastedData.some((value) => isNaN(Number(value)))) { @@ -222,14 +226,14 @@ const OTPInput = ({ // Paste data from focused input onwards for (let pos = 0; pos < numInputs; ++pos) { - if (pos >= activeInput && pastedData.length > 0) { - otp[pos] = pastedData.shift() ?? ''; + if (pastedData.length > 0) { + currentOtpValue[pos] = pastedData.shift() ?? ''; nextActiveInput++; } } focusInput(nextActiveInput); - handleOTPChange(otp); + handleOTPChange(currentOtpValue); }; return ( @@ -242,7 +246,7 @@ const OTPInput = ({ {renderInput( { - value: getOTPValue()[index] ?? '', + value: otpValueRef.current[index] ?? '', placeholder: getPlaceholderValue()?.[index] ?? undefined, ref: (element) => (inputRefs.current[index] = element), onChange: handleChange, From 67e61959d1fcff1a9718d16b6234a6da70df31e8 Mon Sep 17 00:00:00 2001 From: Ahmed Saeed Date: Thu, 11 Jul 2024 17:42:53 +0300 Subject: [PATCH 2/3] fix: Assign the pasted value from the clipboard auto paste to the otpValueRef to reflect on the input value --- src/index.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/index.tsx b/src/index.tsx index 4199286..7c1390d 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -129,6 +129,7 @@ const OTPInput = ({ if (!hasInvalidInput) { handleOTPChange(value.split('')); focusInput(numInputs - 1); + otpValueRef.current = valueArr; } } From 12b4acf2edd225ce5ef8f3280124a27c6e0e2967 Mon Sep 17 00:00:00 2001 From: Ahmed Saeed Date: Thu, 11 Jul 2024 17:43:46 +0300 Subject: [PATCH 3/3] feat: Handle when the pasted value is longer or shorter than the inputs number feat: Handle when the pasted value is longer than the inputs number style: Remove unused useRef import fix: Pasting fewer value than numInputs doesn't work --- src/index.tsx | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/index.tsx b/src/index.tsx index 7c1390d..8e5c8a4 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,4 +1,4 @@ -import React, { useRef } from 'react'; +import React from 'react'; type AllowedInputTypes = 'password' | 'text' | 'number' | 'tel'; @@ -124,11 +124,13 @@ const OTPInput = ({ // Pasting from the native autofill suggestion on a mobile device can pass // the pasted string as one long input to one of the cells. This ensures // that we handle the full input and not just the first character. - if (value.length === numInputs) { - const hasInvalidInput = value.split('').some((cellInput) => !isInputValueValid(cellInput)); + + if (value.length > 1) { + const valueArr = value.split('', numInputs); + const hasInvalidInput = valueArr.some((cellInput) => !isInputValueValid(cellInput)); if (!hasInvalidInput) { - handleOTPChange(value.split('')); - focusInput(numInputs - 1); + handleOTPChange(valueArr); + focusInput(value.length - 1); otpValueRef.current = valueArr; } }