Skip to content

Commit 44b3ace

Browse files
committed
fix: auto insert OTP not working
Ue refs instead of state
1 parent 4b56c82 commit 44b3ace

File tree

1 file changed

+31
-22
lines changed

1 file changed

+31
-22
lines changed

src/components/MagicCodeInput.tsx

+31-22
Original file line numberDiff line numberDiff line change
@@ -114,44 +114,45 @@ 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

131132
const blurMagicCodeInput = () => {
132-
inputRefs.current?.blur();
133+
inputRef.current?.blur();
133134
setFocusedIndex(undefined);
134135
};
135136

136137
const focusMagicCodeInput = () => {
137138
setFocusedIndex(0);
138139
lastFocusedIndex.current = 0;
139-
setEditIndex(0);
140-
inputRefs.current?.focus();
140+
editIndex.current = 0;
141+
inputRef.current?.focus();
141142
};
142143

143144
const setInputAndIndex = (index: number) => {
144145
setInput(TEXT_INPUT_EMPTY_STATE);
145146
setFocusedIndex(index);
146-
setEditIndex(index);
147+
editIndex.current = index;
147148
};
148149

149150
useImperativeHandle(ref, () => ({
150151
focus() {
151152
focusMagicCodeInput();
152153
},
153154
focusLastSelected() {
154-
inputRefs.current?.focus();
155+
inputRef.current?.focus();
155156
},
156157
resetFocus() {
157158
setInput(TEXT_INPUT_EMPTY_STATE);
@@ -160,7 +161,7 @@ function MagicCodeInput(
160161
clear() {
161162
lastFocusedIndex.current = 0;
162163
setInputAndIndex(0);
163-
inputRefs.current?.focus();
164+
inputRef.current?.focus();
164165
onChangeTextProp('');
165166
},
166167
blur() {
@@ -219,7 +220,7 @@ function MagicCodeInput(
219220
// TapGestureHandler works differently on mobile web and native app
220221
// On web gesture handler doesn't block interactions with textInput below so there is no need to run `focus()` manually
221222
if (!Browser.isMobileChrome() && !Browser.isMobileSafari()) {
222-
inputRefs.current?.focus();
223+
inputRef.current?.focus();
223224
}
224225
setInputAndIndex(index);
225226
lastFocusedIndex.current = index;
@@ -231,6 +232,12 @@ function MagicCodeInput(
231232
* the focused input on the next empty one, if exists.
232233
* It handles both fast typing and only one digit at a time
233234
* in a specific position.
235+
*
236+
* Note: this works under the assumption that the backing text input will always have a cleared text,
237+
* and entering text will exactly call onChangeText with one new character/digit.
238+
* When the OS is inserting one time passwords for example it will call this method successively with one more digit each time.
239+
* Thus, this method relies on an internal value ref to make sure to always use the latest value (as state updates are async, and
240+
* might happen later than the next call to onChangeText).
234241
*/
235242
const onChangeText = (textValue?: string) => {
236243
if (!textValue?.length || !ValidationUtils.isNumeric(textValue)) {
@@ -249,16 +256,17 @@ function MagicCodeInput(
249256
const numbersArr = addedValue
250257
.trim()
251258
.split('')
252-
.slice(0, maxLength - editIndex);
253-
const updatedFocusedIndex = Math.min(editIndex + (numbersArr.length - 1) + 1, maxLength - 1);
259+
.slice(0, maxLength - editIndex.current);
260+
const updatedFocusedIndex = Math.min(editIndex.current + (numbersArr.length - 1) + 1, maxLength - 1);
254261

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

258265
setInputAndIndex(updatedFocusedIndex);
259266

260267
const finalInput = composeToString(numbers);
261268
onChangeTextProp(finalInput);
269+
valueRef.current = finalInput;
262270
};
263271

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

280-
const indexToFocus = numbers.at(editIndex) === CONST.MAGIC_CODE_EMPTY_CHAR ? indexBeforeLastEditIndex : editIndex;
289+
const indexToFocus = numbers.at(curEditIndex) === CONST.MAGIC_CODE_EMPTY_CHAR ? indexBeforeLastEditIndex : curEditIndex;
281290
if (indexToFocus !== undefined) {
282291
lastFocusedIndex.current = indexToFocus;
283-
inputRefs.current?.focus();
292+
inputRef.current?.focus();
284293
}
285294
onChangeTextProp(value.substring(0, indexToFocus));
286295

@@ -292,7 +301,7 @@ function MagicCodeInput(
292301
if (focusedIndex !== undefined && numbers?.at(focusedIndex) !== CONST.MAGIC_CODE_EMPTY_CHAR) {
293302
setInput(TEXT_INPUT_EMPTY_STATE);
294303
numbers = [...numbers.slice(0, focusedIndex), CONST.MAGIC_CODE_EMPTY_CHAR, ...numbers.slice(focusedIndex + 1, maxLength)];
295-
setEditIndex(focusedIndex);
304+
editIndex.current = focusedIndex;
296305
onChangeTextProp(composeToString(numbers));
297306
return;
298307
}
@@ -318,17 +327,17 @@ function MagicCodeInput(
318327

319328
if (newFocusedIndex !== undefined) {
320329
lastFocusedIndex.current = newFocusedIndex;
321-
inputRefs.current?.focus();
330+
inputRef.current?.focus();
322331
}
323332
}
324333
if (keyValue === 'ArrowLeft' && focusedIndex !== undefined) {
325334
const newFocusedIndex = Math.max(0, focusedIndex - 1);
326335
setInputAndIndex(newFocusedIndex);
327-
inputRefs.current?.focus();
336+
inputRef.current?.focus();
328337
} else if (keyValue === 'ArrowRight' && focusedIndex !== undefined) {
329338
const newFocusedIndex = Math.min(focusedIndex + 1, maxLength - 1);
330339
setInputAndIndex(newFocusedIndex);
331-
inputRefs.current?.focus();
340+
inputRef.current?.focus();
332341
} else if (keyValue === 'Enter') {
333342
// We should prevent users from submitting when it's offline.
334343
if (isOffline) {
@@ -340,7 +349,7 @@ function MagicCodeInput(
340349
const newFocusedIndex = (event as unknown as KeyboardEvent).shiftKey ? focusedIndex - 1 : focusedIndex + 1;
341350
if (newFocusedIndex >= 0 && newFocusedIndex < maxLength) {
342351
setInputAndIndex(newFocusedIndex);
343-
inputRefs.current?.focus();
352+
inputRef.current?.focus();
344353
if (event?.preventDefault) {
345354
event.preventDefault();
346355
}
@@ -383,7 +392,7 @@ function MagicCodeInput(
383392
onLayout={(e) => {
384393
inputWidth.current = e.nativeEvent.layout.width;
385394
}}
386-
ref={(inputRef) => (inputRefs.current = inputRef)}
395+
ref={(newRef) => (inputRef.current = newRef)}
387396
autoFocus={autoFocus}
388397
inputMode="numeric"
389398
textContentType="oneTimeCode"

0 commit comments

Comments
 (0)