Skip to content

Commit b7b5dda

Browse files
authored
Merge pull request #54253 from margelo/fix/otp-inserting-now-working
fix: auto insert OTP not working
2 parents 9412d21 + eedaac8 commit b7b5dda

File tree

1 file changed

+38
-22
lines changed

1 file changed

+38
-22
lines changed

src/components/MagicCodeInput.tsx

+38-22
Original file line numberDiff line numberDiff line change
@@ -114,44 +114,52 @@ function MagicCodeInput(
114114
) {
115115
const styles = useThemeStyles();
116116
const StyleUtils = useStyleUtils();
117-
const inputRefs = useRef<BaseTextInputRef | null>();
117+
const inputRef = useRef<BaseTextInputRef | null>();
118118
const [input, setInput] = useState(TEXT_INPUT_EMPTY_STATE);
119119
const [focusedIndex, setFocusedIndex] = useState<number | undefined>(0);
120-
const [editIndex, setEditIndex] = useState(0);
120+
const editIndex = useRef(0);
121121
const [wasSubmitted, setWasSubmitted] = useState(false);
122122
const shouldFocusLast = useRef(false);
123123
const inputWidth = useRef(0);
124124
const lastFocusedIndex = useRef(0);
125125
const lastValue = useRef<string | number>(TEXT_INPUT_EMPTY_STATE);
126+
const valueRef = useRef(value);
126127

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

132+
useEffect(() => {
133+
// Note: there are circumstances where the value state isn't updated yet
134+
// when e.g. onChangeText gets called the next time. In those cases its safer to access the value from a ref
135+
// to not have outdated values.
136+
valueRef.current = value;
137+
}, [value]);
138+
131139
const blurMagicCodeInput = () => {
132-
inputRefs.current?.blur();
140+
inputRef.current?.blur();
133141
setFocusedIndex(undefined);
134142
};
135143

136144
const focusMagicCodeInput = () => {
137145
setFocusedIndex(0);
138146
lastFocusedIndex.current = 0;
139-
setEditIndex(0);
140-
inputRefs.current?.focus();
147+
editIndex.current = 0;
148+
inputRef.current?.focus();
141149
};
142150

143151
const setInputAndIndex = (index: number) => {
144152
setInput(TEXT_INPUT_EMPTY_STATE);
145153
setFocusedIndex(index);
146-
setEditIndex(index);
154+
editIndex.current = index;
147155
};
148156

149157
useImperativeHandle(ref, () => ({
150158
focus() {
151159
focusMagicCodeInput();
152160
},
153161
focusLastSelected() {
154-
inputRefs.current?.focus();
162+
inputRef.current?.focus();
155163
},
156164
resetFocus() {
157165
setInput(TEXT_INPUT_EMPTY_STATE);
@@ -160,7 +168,7 @@ function MagicCodeInput(
160168
clear() {
161169
lastFocusedIndex.current = 0;
162170
setInputAndIndex(0);
163-
inputRefs.current?.focus();
171+
inputRef.current?.focus();
164172
onChangeTextProp('');
165173
},
166174
blur() {
@@ -219,7 +227,7 @@ function MagicCodeInput(
219227
// TapGestureHandler works differently on mobile web and native app
220228
// On web gesture handler doesn't block interactions with textInput below so there is no need to run `focus()` manually
221229
if (!Browser.isMobileChrome() && !Browser.isMobileSafari()) {
222-
inputRefs.current?.focus();
230+
inputRef.current?.focus();
223231
}
224232
setInputAndIndex(index);
225233
lastFocusedIndex.current = index;
@@ -231,6 +239,12 @@ function MagicCodeInput(
231239
* the focused input on the next empty one, if exists.
232240
* It handles both fast typing and only one digit at a time
233241
* in a specific position.
242+
*
243+
* Note: this works under the assumption that the backing text input will always have a cleared text,
244+
* and entering text will exactly call onChangeText with one new character/digit.
245+
* When the OS is inserting one time passwords for example it will call this method successively with one more digit each time.
246+
* Thus, this method relies on an internal value ref to make sure to always use the latest value (as state updates are async, and
247+
* might happen later than the next call to onChangeText).
234248
*/
235249
const onChangeText = (textValue?: string) => {
236250
if (!textValue?.length || !ValidationUtils.isNumeric(textValue)) {
@@ -249,16 +263,17 @@ function MagicCodeInput(
249263
const numbersArr = addedValue
250264
.trim()
251265
.split('')
252-
.slice(0, maxLength - editIndex);
253-
const updatedFocusedIndex = Math.min(editIndex + (numbersArr.length - 1) + 1, maxLength - 1);
266+
.slice(0, maxLength - editIndex.current);
267+
const updatedFocusedIndex = Math.min(editIndex.current + (numbersArr.length - 1) + 1, maxLength - 1);
254268

255-
let numbers = decomposeString(value, maxLength);
256-
numbers = [...numbers.slice(0, editIndex), ...numbersArr, ...numbers.slice(numbersArr.length + editIndex, maxLength)];
269+
let numbers = decomposeString(valueRef.current, maxLength);
270+
numbers = [...numbers.slice(0, editIndex.current), ...numbersArr, ...numbers.slice(numbersArr.length + editIndex.current, maxLength)];
257271

258272
setInputAndIndex(updatedFocusedIndex);
259273

260274
const finalInput = composeToString(numbers);
261275
onChangeTextProp(finalInput);
276+
valueRef.current = finalInput;
262277
};
263278

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

280-
const indexToFocus = numbers.at(editIndex) === CONST.MAGIC_CODE_EMPTY_CHAR ? indexBeforeLastEditIndex : editIndex;
296+
const indexToFocus = numbers.at(curEditIndex) === CONST.MAGIC_CODE_EMPTY_CHAR ? indexBeforeLastEditIndex : curEditIndex;
281297
if (indexToFocus !== undefined) {
282298
lastFocusedIndex.current = indexToFocus;
283-
inputRefs.current?.focus();
299+
inputRef.current?.focus();
284300
}
285301
onChangeTextProp(value.substring(0, indexToFocus));
286302

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

319335
if (newFocusedIndex !== undefined) {
320336
lastFocusedIndex.current = newFocusedIndex;
321-
inputRefs.current?.focus();
337+
inputRef.current?.focus();
322338
}
323339
}
324340
if (keyValue === 'ArrowLeft' && focusedIndex !== undefined) {
325341
const newFocusedIndex = Math.max(0, focusedIndex - 1);
326342
setInputAndIndex(newFocusedIndex);
327-
inputRefs.current?.focus();
343+
inputRef.current?.focus();
328344
} else if (keyValue === 'ArrowRight' && focusedIndex !== undefined) {
329345
const newFocusedIndex = Math.min(focusedIndex + 1, maxLength - 1);
330346
setInputAndIndex(newFocusedIndex);
331-
inputRefs.current?.focus();
347+
inputRef.current?.focus();
332348
} else if (keyValue === 'Enter') {
333349
// We should prevent users from submitting when it's offline.
334350
if (isOffline) {
@@ -340,7 +356,7 @@ function MagicCodeInput(
340356
const newFocusedIndex = (event as unknown as KeyboardEvent).shiftKey ? focusedIndex - 1 : focusedIndex + 1;
341357
if (newFocusedIndex >= 0 && newFocusedIndex < maxLength) {
342358
setInputAndIndex(newFocusedIndex);
343-
inputRefs.current?.focus();
359+
inputRef.current?.focus();
344360
if (event?.preventDefault) {
345361
event.preventDefault();
346362
}
@@ -383,7 +399,7 @@ function MagicCodeInput(
383399
onLayout={(e) => {
384400
inputWidth.current = e.nativeEvent.layout.width;
385401
}}
386-
ref={(inputRef) => (inputRefs.current = inputRef)}
402+
ref={(newRef) => (inputRef.current = newRef)}
387403
autoFocus={autoFocus}
388404
inputMode="numeric"
389405
textContentType="oneTimeCode"

0 commit comments

Comments
 (0)