@@ -114,44 +114,45 @@ function MagicCodeInput(
114
114
) {
115
115
const styles = useThemeStyles ( ) ;
116
116
const StyleUtils = useStyleUtils ( ) ;
117
- const inputRefs = useRef < BaseTextInputRef | null > ( ) ;
117
+ const inputRef = useRef < BaseTextInputRef | null > ( ) ;
118
118
const [ input , setInput ] = useState ( TEXT_INPUT_EMPTY_STATE ) ;
119
119
const [ focusedIndex , setFocusedIndex ] = useState < number | undefined > ( 0 ) ;
120
- const [ editIndex , setEditIndex ] = useState ( 0 ) ;
120
+ const editIndex = useRef ( 0 ) ;
121
121
const [ wasSubmitted , setWasSubmitted ] = useState ( false ) ;
122
122
const shouldFocusLast = useRef ( false ) ;
123
123
const inputWidth = useRef ( 0 ) ;
124
124
const lastFocusedIndex = useRef ( 0 ) ;
125
125
const lastValue = useRef < string | number > ( TEXT_INPUT_EMPTY_STATE ) ;
126
+ const valueRef = useRef ( value ) ;
126
127
127
128
useEffect ( ( ) => {
128
129
lastValue . current = input . length ;
129
130
} , [ input ] ) ;
130
131
131
132
const blurMagicCodeInput = ( ) => {
132
- inputRefs . current ?. blur ( ) ;
133
+ inputRef . current ?. blur ( ) ;
133
134
setFocusedIndex ( undefined ) ;
134
135
} ;
135
136
136
137
const focusMagicCodeInput = ( ) => {
137
138
setFocusedIndex ( 0 ) ;
138
139
lastFocusedIndex . current = 0 ;
139
- setEditIndex ( 0 ) ;
140
- inputRefs . current ?. focus ( ) ;
140
+ editIndex . current = 0 ;
141
+ inputRef . current ?. focus ( ) ;
141
142
} ;
142
143
143
144
const setInputAndIndex = ( index : number ) => {
144
145
setInput ( TEXT_INPUT_EMPTY_STATE ) ;
145
146
setFocusedIndex ( index ) ;
146
- setEditIndex ( index ) ;
147
+ editIndex . current = index ;
147
148
} ;
148
149
149
150
useImperativeHandle ( ref , ( ) => ( {
150
151
focus ( ) {
151
152
focusMagicCodeInput ( ) ;
152
153
} ,
153
154
focusLastSelected ( ) {
154
- inputRefs . current ?. focus ( ) ;
155
+ inputRef . current ?. focus ( ) ;
155
156
} ,
156
157
resetFocus ( ) {
157
158
setInput ( TEXT_INPUT_EMPTY_STATE ) ;
@@ -160,7 +161,7 @@ function MagicCodeInput(
160
161
clear ( ) {
161
162
lastFocusedIndex . current = 0 ;
162
163
setInputAndIndex ( 0 ) ;
163
- inputRefs . current ?. focus ( ) ;
164
+ inputRef . current ?. focus ( ) ;
164
165
onChangeTextProp ( '' ) ;
165
166
} ,
166
167
blur ( ) {
@@ -219,7 +220,7 @@ function MagicCodeInput(
219
220
// TapGestureHandler works differently on mobile web and native app
220
221
// On web gesture handler doesn't block interactions with textInput below so there is no need to run `focus()` manually
221
222
if ( ! Browser . isMobileChrome ( ) && ! Browser . isMobileSafari ( ) ) {
222
- inputRefs . current ?. focus ( ) ;
223
+ inputRef . current ?. focus ( ) ;
223
224
}
224
225
setInputAndIndex ( index ) ;
225
226
lastFocusedIndex . current = index ;
@@ -231,6 +232,12 @@ function MagicCodeInput(
231
232
* the focused input on the next empty one, if exists.
232
233
* It handles both fast typing and only one digit at a time
233
234
* 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).
234
241
*/
235
242
const onChangeText = ( textValue ?: string ) => {
236
243
if ( ! textValue ?. length || ! ValidationUtils . isNumeric ( textValue ) ) {
@@ -249,16 +256,17 @@ function MagicCodeInput(
249
256
const numbersArr = addedValue
250
257
. trim ( )
251
258
. 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 ) ;
254
261
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 ) ] ;
257
264
258
265
setInputAndIndex ( updatedFocusedIndex ) ;
259
266
260
267
const finalInput = composeToString ( numbers ) ;
261
268
onChangeTextProp ( finalInput ) ;
269
+ valueRef . current = finalInput ;
262
270
} ;
263
271
264
272
/**
@@ -275,12 +283,13 @@ function MagicCodeInput(
275
283
// If keyboard is disabled and no input is focused we need to remove
276
284
// the last entered digit and focus on the correct input
277
285
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 ;
279
288
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 ;
281
290
if ( indexToFocus !== undefined ) {
282
291
lastFocusedIndex . current = indexToFocus ;
283
- inputRefs . current ?. focus ( ) ;
292
+ inputRef . current ?. focus ( ) ;
284
293
}
285
294
onChangeTextProp ( value . substring ( 0 , indexToFocus ) ) ;
286
295
@@ -292,7 +301,7 @@ function MagicCodeInput(
292
301
if ( focusedIndex !== undefined && numbers ?. at ( focusedIndex ) !== CONST . MAGIC_CODE_EMPTY_CHAR ) {
293
302
setInput ( TEXT_INPUT_EMPTY_STATE ) ;
294
303
numbers = [ ...numbers . slice ( 0 , focusedIndex ) , CONST . MAGIC_CODE_EMPTY_CHAR , ...numbers . slice ( focusedIndex + 1 , maxLength ) ] ;
295
- setEditIndex ( focusedIndex ) ;
304
+ editIndex . current = focusedIndex ;
296
305
onChangeTextProp ( composeToString ( numbers ) ) ;
297
306
return ;
298
307
}
@@ -318,17 +327,17 @@ function MagicCodeInput(
318
327
319
328
if ( newFocusedIndex !== undefined ) {
320
329
lastFocusedIndex . current = newFocusedIndex ;
321
- inputRefs . current ?. focus ( ) ;
330
+ inputRef . current ?. focus ( ) ;
322
331
}
323
332
}
324
333
if ( keyValue === 'ArrowLeft' && focusedIndex !== undefined ) {
325
334
const newFocusedIndex = Math . max ( 0 , focusedIndex - 1 ) ;
326
335
setInputAndIndex ( newFocusedIndex ) ;
327
- inputRefs . current ?. focus ( ) ;
336
+ inputRef . current ?. focus ( ) ;
328
337
} else if ( keyValue === 'ArrowRight' && focusedIndex !== undefined ) {
329
338
const newFocusedIndex = Math . min ( focusedIndex + 1 , maxLength - 1 ) ;
330
339
setInputAndIndex ( newFocusedIndex ) ;
331
- inputRefs . current ?. focus ( ) ;
340
+ inputRef . current ?. focus ( ) ;
332
341
} else if ( keyValue === 'Enter' ) {
333
342
// We should prevent users from submitting when it's offline.
334
343
if ( isOffline ) {
@@ -340,7 +349,7 @@ function MagicCodeInput(
340
349
const newFocusedIndex = ( event as unknown as KeyboardEvent ) . shiftKey ? focusedIndex - 1 : focusedIndex + 1 ;
341
350
if ( newFocusedIndex >= 0 && newFocusedIndex < maxLength ) {
342
351
setInputAndIndex ( newFocusedIndex ) ;
343
- inputRefs . current ?. focus ( ) ;
352
+ inputRef . current ?. focus ( ) ;
344
353
if ( event ?. preventDefault ) {
345
354
event . preventDefault ( ) ;
346
355
}
@@ -383,7 +392,7 @@ function MagicCodeInput(
383
392
onLayout = { ( e ) => {
384
393
inputWidth . current = e . nativeEvent . layout . width ;
385
394
} }
386
- ref = { ( inputRef ) => ( inputRefs . current = inputRef ) }
395
+ ref = { ( newRef ) => ( inputRef . current = newRef ) }
387
396
autoFocus = { autoFocus }
388
397
inputMode = "numeric"
389
398
textContentType = "oneTimeCode"
0 commit comments