Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: auto insert OTP not working #54253

Merged
merged 1 commit into from
Dec 28, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
fix: auto insert OTP not working
Ue refs instead of state
  • Loading branch information
hannojg committed Dec 17, 2024

Verified

This commit was signed with the committer’s verified signature.
BartoszGrajdek Bartosz Grajdek
commit eedaac8a1339575a469a7afe6415e82e37abc52b
60 changes: 38 additions & 22 deletions src/components/MagicCodeInput.tsx
Original file line number Diff line number Diff line change
@@ -114,44 +114,52 @@ function MagicCodeInput(
) {
const styles = useThemeStyles();
const StyleUtils = useStyleUtils();
const inputRefs = useRef<BaseTextInputRef | null>();
const inputRef = useRef<BaseTextInputRef | null>();
const [input, setInput] = useState(TEXT_INPUT_EMPTY_STATE);
const [focusedIndex, setFocusedIndex] = useState<number | undefined>(0);
const [editIndex, setEditIndex] = useState(0);
const editIndex = useRef(0);
const [wasSubmitted, setWasSubmitted] = useState(false);
const shouldFocusLast = useRef(false);
const inputWidth = useRef(0);
const lastFocusedIndex = useRef(0);
const lastValue = useRef<string | number>(TEXT_INPUT_EMPTY_STATE);
const valueRef = useRef(value);

useEffect(() => {
lastValue.current = input.length;
}, [input]);

useEffect(() => {
// Note: there are circumstances where the value state isn't updated yet
// when e.g. onChangeText gets called the next time. In those cases its safer to access the value from a ref
// to not have outdated values.
valueRef.current = value;
}, [value]);

const blurMagicCodeInput = () => {
inputRefs.current?.blur();
inputRef.current?.blur();
setFocusedIndex(undefined);
};

const focusMagicCodeInput = () => {
setFocusedIndex(0);
lastFocusedIndex.current = 0;
setEditIndex(0);
inputRefs.current?.focus();
editIndex.current = 0;
inputRef.current?.focus();
};

const setInputAndIndex = (index: number) => {
setInput(TEXT_INPUT_EMPTY_STATE);
setFocusedIndex(index);
setEditIndex(index);
editIndex.current = index;
};

useImperativeHandle(ref, () => ({
focus() {
focusMagicCodeInput();
},
focusLastSelected() {
inputRefs.current?.focus();
inputRef.current?.focus();
},
resetFocus() {
setInput(TEXT_INPUT_EMPTY_STATE);
@@ -160,7 +168,7 @@ function MagicCodeInput(
clear() {
lastFocusedIndex.current = 0;
setInputAndIndex(0);
inputRefs.current?.focus();
inputRef.current?.focus();
onChangeTextProp('');
},
blur() {
@@ -219,7 +227,7 @@ function MagicCodeInput(
// TapGestureHandler works differently on mobile web and native app
// On web gesture handler doesn't block interactions with textInput below so there is no need to run `focus()` manually
if (!Browser.isMobileChrome() && !Browser.isMobileSafari()) {
inputRefs.current?.focus();
inputRef.current?.focus();
}
setInputAndIndex(index);
lastFocusedIndex.current = index;
@@ -231,6 +239,12 @@ function MagicCodeInput(
* the focused input on the next empty one, if exists.
* It handles both fast typing and only one digit at a time
* in a specific position.
*
* Note: this works under the assumption that the backing text input will always have a cleared text,
* and entering text will exactly call onChangeText with one new character/digit.
* When the OS is inserting one time passwords for example it will call this method successively with one more digit each time.
* Thus, this method relies on an internal value ref to make sure to always use the latest value (as state updates are async, and
* might happen later than the next call to onChangeText).
*/
const onChangeText = (textValue?: string) => {
if (!textValue?.length || !ValidationUtils.isNumeric(textValue)) {
@@ -249,16 +263,17 @@ function MagicCodeInput(
const numbersArr = addedValue
.trim()
.split('')
.slice(0, maxLength - editIndex);
const updatedFocusedIndex = Math.min(editIndex + (numbersArr.length - 1) + 1, maxLength - 1);
.slice(0, maxLength - editIndex.current);
const updatedFocusedIndex = Math.min(editIndex.current + (numbersArr.length - 1) + 1, maxLength - 1);

let numbers = decomposeString(value, maxLength);
numbers = [...numbers.slice(0, editIndex), ...numbersArr, ...numbers.slice(numbersArr.length + editIndex, maxLength)];
let numbers = decomposeString(valueRef.current, maxLength);
numbers = [...numbers.slice(0, editIndex.current), ...numbersArr, ...numbers.slice(numbersArr.length + editIndex.current, maxLength)];

setInputAndIndex(updatedFocusedIndex);

const finalInput = composeToString(numbers);
onChangeTextProp(finalInput);
valueRef.current = finalInput;
};

/**
@@ -275,12 +290,13 @@ function MagicCodeInput(
// If keyboard is disabled and no input is focused we need to remove
// the last entered digit and focus on the correct input
if (isDisableKeyboard && focusedIndex === undefined) {
const indexBeforeLastEditIndex = editIndex === 0 ? editIndex : editIndex - 1;
const curEditIndex = editIndex.current;
const indexBeforeLastEditIndex = curEditIndex === 0 ? curEditIndex : curEditIndex - 1;

const indexToFocus = numbers.at(editIndex) === CONST.MAGIC_CODE_EMPTY_CHAR ? indexBeforeLastEditIndex : editIndex;
const indexToFocus = numbers.at(curEditIndex) === CONST.MAGIC_CODE_EMPTY_CHAR ? indexBeforeLastEditIndex : curEditIndex;
if (indexToFocus !== undefined) {
lastFocusedIndex.current = indexToFocus;
inputRefs.current?.focus();
inputRef.current?.focus();
}
onChangeTextProp(value.substring(0, indexToFocus));

@@ -292,7 +308,7 @@ function MagicCodeInput(
if (focusedIndex !== undefined && numbers?.at(focusedIndex) !== CONST.MAGIC_CODE_EMPTY_CHAR) {
setInput(TEXT_INPUT_EMPTY_STATE);
numbers = [...numbers.slice(0, focusedIndex), CONST.MAGIC_CODE_EMPTY_CHAR, ...numbers.slice(focusedIndex + 1, maxLength)];
setEditIndex(focusedIndex);
editIndex.current = focusedIndex;
onChangeTextProp(composeToString(numbers));
return;
}
@@ -318,17 +334,17 @@ function MagicCodeInput(

if (newFocusedIndex !== undefined) {
lastFocusedIndex.current = newFocusedIndex;
inputRefs.current?.focus();
inputRef.current?.focus();
}
}
if (keyValue === 'ArrowLeft' && focusedIndex !== undefined) {
const newFocusedIndex = Math.max(0, focusedIndex - 1);
setInputAndIndex(newFocusedIndex);
inputRefs.current?.focus();
inputRef.current?.focus();
} else if (keyValue === 'ArrowRight' && focusedIndex !== undefined) {
const newFocusedIndex = Math.min(focusedIndex + 1, maxLength - 1);
setInputAndIndex(newFocusedIndex);
inputRefs.current?.focus();
inputRef.current?.focus();
} else if (keyValue === 'Enter') {
// We should prevent users from submitting when it's offline.
if (isOffline) {
@@ -340,7 +356,7 @@ function MagicCodeInput(
const newFocusedIndex = (event as unknown as KeyboardEvent).shiftKey ? focusedIndex - 1 : focusedIndex + 1;
if (newFocusedIndex >= 0 && newFocusedIndex < maxLength) {
setInputAndIndex(newFocusedIndex);
inputRefs.current?.focus();
inputRef.current?.focus();
if (event?.preventDefault) {
event.preventDefault();
}
@@ -383,7 +399,7 @@ function MagicCodeInput(
onLayout={(e) => {
inputWidth.current = e.nativeEvent.layout.width;
}}
ref={(inputRef) => (inputRefs.current = inputRef)}
ref={(newRef) => (inputRef.current = newRef)}
autoFocus={autoFocus}
inputMode="numeric"
textContentType="oneTimeCode"