Skip to content

Commit 7397487

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

File tree

1 file changed

+35
-22
lines changed

1 file changed

+35
-22
lines changed

src/components/MagicCodeInput.tsx

+35-22
Original file line numberDiff line numberDiff line change
@@ -114,44 +114,49 @@ 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+
valueRef.current = value;
134+
}, [value]);
135+
131136
const blurMagicCodeInput = () => {
132-
inputRefs.current?.blur();
137+
inputRef.current?.blur();
133138
setFocusedIndex(undefined);
134139
};
135140

136141
const focusMagicCodeInput = () => {
137142
setFocusedIndex(0);
138143
lastFocusedIndex.current = 0;
139-
setEditIndex(0);
140-
inputRefs.current?.focus();
144+
editIndex.current = 0;
145+
inputRef.current?.focus();
141146
};
142147

143148
const setInputAndIndex = (index: number) => {
144149
setInput(TEXT_INPUT_EMPTY_STATE);
145150
setFocusedIndex(index);
146-
setEditIndex(index);
151+
editIndex.current = index;
147152
};
148153

149154
useImperativeHandle(ref, () => ({
150155
focus() {
151156
focusMagicCodeInput();
152157
},
153158
focusLastSelected() {
154-
inputRefs.current?.focus();
159+
inputRef.current?.focus();
155160
},
156161
resetFocus() {
157162
setInput(TEXT_INPUT_EMPTY_STATE);
@@ -160,7 +165,7 @@ function MagicCodeInput(
160165
clear() {
161166
lastFocusedIndex.current = 0;
162167
setInputAndIndex(0);
163-
inputRefs.current?.focus();
168+
inputRef.current?.focus();
164169
onChangeTextProp('');
165170
},
166171
blur() {
@@ -219,7 +224,7 @@ function MagicCodeInput(
219224
// TapGestureHandler works differently on mobile web and native app
220225
// On web gesture handler doesn't block interactions with textInput below so there is no need to run `focus()` manually
221226
if (!Browser.isMobileChrome() && !Browser.isMobileSafari()) {
222-
inputRefs.current?.focus();
227+
inputRef.current?.focus();
223228
}
224229
setInputAndIndex(index);
225230
lastFocusedIndex.current = index;
@@ -231,6 +236,12 @@ function MagicCodeInput(
231236
* the focused input on the next empty one, if exists.
232237
* It handles both fast typing and only one digit at a time
233238
* in a specific position.
239+
*
240+
* Note: this works under the assumption that the backing text input will always have a cleared text,
241+
* and entering text will exactly call onChangeText with one new character/digit.
242+
* When the OS is inserting one time passwords for example it will call this method successively with one more digit each time.
243+
* Thus, this method relies on an internal value ref to make sure to always use the latest value (as state updates are async, and
244+
* might happen later than the next call to onChangeText).
234245
*/
235246
const onChangeText = (textValue?: string) => {
236247
if (!textValue?.length || !ValidationUtils.isNumeric(textValue)) {
@@ -249,16 +260,17 @@ function MagicCodeInput(
249260
const numbersArr = addedValue
250261
.trim()
251262
.split('')
252-
.slice(0, maxLength - editIndex);
253-
const updatedFocusedIndex = Math.min(editIndex + (numbersArr.length - 1) + 1, maxLength - 1);
263+
.slice(0, maxLength - editIndex.current);
264+
const updatedFocusedIndex = Math.min(editIndex.current + (numbersArr.length - 1) + 1, maxLength - 1);
254265

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

258269
setInputAndIndex(updatedFocusedIndex);
259270

260271
const finalInput = composeToString(numbers);
261272
onChangeTextProp(finalInput);
273+
valueRef.current = finalInput;
262274
};
263275

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

280-
const indexToFocus = numbers.at(editIndex) === CONST.MAGIC_CODE_EMPTY_CHAR ? indexBeforeLastEditIndex : editIndex;
293+
const indexToFocus = numbers.at(curEditIndex) === CONST.MAGIC_CODE_EMPTY_CHAR ? indexBeforeLastEditIndex : curEditIndex;
281294
if (indexToFocus !== undefined) {
282295
lastFocusedIndex.current = indexToFocus;
283-
inputRefs.current?.focus();
296+
inputRef.current?.focus();
284297
}
285298
onChangeTextProp(value.substring(0, indexToFocus));
286299

@@ -292,7 +305,7 @@ function MagicCodeInput(
292305
if (focusedIndex !== undefined && numbers?.at(focusedIndex) !== CONST.MAGIC_CODE_EMPTY_CHAR) {
293306
setInput(TEXT_INPUT_EMPTY_STATE);
294307
numbers = [...numbers.slice(0, focusedIndex), CONST.MAGIC_CODE_EMPTY_CHAR, ...numbers.slice(focusedIndex + 1, maxLength)];
295-
setEditIndex(focusedIndex);
308+
editIndex.current = focusedIndex;
296309
onChangeTextProp(composeToString(numbers));
297310
return;
298311
}
@@ -318,17 +331,17 @@ function MagicCodeInput(
318331

319332
if (newFocusedIndex !== undefined) {
320333
lastFocusedIndex.current = newFocusedIndex;
321-
inputRefs.current?.focus();
334+
inputRef.current?.focus();
322335
}
323336
}
324337
if (keyValue === 'ArrowLeft' && focusedIndex !== undefined) {
325338
const newFocusedIndex = Math.max(0, focusedIndex - 1);
326339
setInputAndIndex(newFocusedIndex);
327-
inputRefs.current?.focus();
340+
inputRef.current?.focus();
328341
} else if (keyValue === 'ArrowRight' && focusedIndex !== undefined) {
329342
const newFocusedIndex = Math.min(focusedIndex + 1, maxLength - 1);
330343
setInputAndIndex(newFocusedIndex);
331-
inputRefs.current?.focus();
344+
inputRef.current?.focus();
332345
} else if (keyValue === 'Enter') {
333346
// We should prevent users from submitting when it's offline.
334347
if (isOffline) {
@@ -340,7 +353,7 @@ function MagicCodeInput(
340353
const newFocusedIndex = (event as unknown as KeyboardEvent).shiftKey ? focusedIndex - 1 : focusedIndex + 1;
341354
if (newFocusedIndex >= 0 && newFocusedIndex < maxLength) {
342355
setInputAndIndex(newFocusedIndex);
343-
inputRefs.current?.focus();
356+
inputRef.current?.focus();
344357
if (event?.preventDefault) {
345358
event.preventDefault();
346359
}
@@ -383,7 +396,7 @@ function MagicCodeInput(
383396
onLayout={(e) => {
384397
inputWidth.current = e.nativeEvent.layout.width;
385398
}}
386-
ref={(inputRef) => (inputRefs.current = inputRef)}
399+
ref={(newRef) => (inputRef.current = newRef)}
387400
autoFocus={autoFocus}
388401
inputMode="numeric"
389402
textContentType="oneTimeCode"

0 commit comments

Comments
 (0)