@@ -114,44 +114,49 @@ 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
132
+ useEffect ( ( ) => {
133
+ valueRef . current = value ;
134
+ } , [ value ] ) ;
135
+
131
136
const blurMagicCodeInput = ( ) => {
132
- inputRefs . current ?. blur ( ) ;
137
+ inputRef . current ?. blur ( ) ;
133
138
setFocusedIndex ( undefined ) ;
134
139
} ;
135
140
136
141
const focusMagicCodeInput = ( ) => {
137
142
setFocusedIndex ( 0 ) ;
138
143
lastFocusedIndex . current = 0 ;
139
- setEditIndex ( 0 ) ;
140
- inputRefs . current ?. focus ( ) ;
144
+ editIndex . current = 0 ;
145
+ inputRef . current ?. focus ( ) ;
141
146
} ;
142
147
143
148
const setInputAndIndex = ( index : number ) => {
144
149
setInput ( TEXT_INPUT_EMPTY_STATE ) ;
145
150
setFocusedIndex ( index ) ;
146
- setEditIndex ( index ) ;
151
+ editIndex . current = index ;
147
152
} ;
148
153
149
154
useImperativeHandle ( ref , ( ) => ( {
150
155
focus ( ) {
151
156
focusMagicCodeInput ( ) ;
152
157
} ,
153
158
focusLastSelected ( ) {
154
- inputRefs . current ?. focus ( ) ;
159
+ inputRef . current ?. focus ( ) ;
155
160
} ,
156
161
resetFocus ( ) {
157
162
setInput ( TEXT_INPUT_EMPTY_STATE ) ;
@@ -160,7 +165,7 @@ function MagicCodeInput(
160
165
clear ( ) {
161
166
lastFocusedIndex . current = 0 ;
162
167
setInputAndIndex ( 0 ) ;
163
- inputRefs . current ?. focus ( ) ;
168
+ inputRef . current ?. focus ( ) ;
164
169
onChangeTextProp ( '' ) ;
165
170
} ,
166
171
blur ( ) {
@@ -219,7 +224,7 @@ function MagicCodeInput(
219
224
// TapGestureHandler works differently on mobile web and native app
220
225
// On web gesture handler doesn't block interactions with textInput below so there is no need to run `focus()` manually
221
226
if ( ! Browser . isMobileChrome ( ) && ! Browser . isMobileSafari ( ) ) {
222
- inputRefs . current ?. focus ( ) ;
227
+ inputRef . current ?. focus ( ) ;
223
228
}
224
229
setInputAndIndex ( index ) ;
225
230
lastFocusedIndex . current = index ;
@@ -231,6 +236,12 @@ function MagicCodeInput(
231
236
* the focused input on the next empty one, if exists.
232
237
* It handles both fast typing and only one digit at a time
233
238
* 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).
234
245
*/
235
246
const onChangeText = ( textValue ?: string ) => {
236
247
if ( ! textValue ?. length || ! ValidationUtils . isNumeric ( textValue ) ) {
@@ -249,16 +260,17 @@ function MagicCodeInput(
249
260
const numbersArr = addedValue
250
261
. trim ( )
251
262
. 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 ) ;
254
265
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 ) ] ;
257
268
258
269
setInputAndIndex ( updatedFocusedIndex ) ;
259
270
260
271
const finalInput = composeToString ( numbers ) ;
261
272
onChangeTextProp ( finalInput ) ;
273
+ valueRef . current = finalInput ;
262
274
} ;
263
275
264
276
/**
@@ -275,12 +287,13 @@ function MagicCodeInput(
275
287
// If keyboard is disabled and no input is focused we need to remove
276
288
// the last entered digit and focus on the correct input
277
289
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 ;
279
292
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 ;
281
294
if ( indexToFocus !== undefined ) {
282
295
lastFocusedIndex . current = indexToFocus ;
283
- inputRefs . current ?. focus ( ) ;
296
+ inputRef . current ?. focus ( ) ;
284
297
}
285
298
onChangeTextProp ( value . substring ( 0 , indexToFocus ) ) ;
286
299
@@ -292,7 +305,7 @@ function MagicCodeInput(
292
305
if ( focusedIndex !== undefined && numbers ?. at ( focusedIndex ) !== CONST . MAGIC_CODE_EMPTY_CHAR ) {
293
306
setInput ( TEXT_INPUT_EMPTY_STATE ) ;
294
307
numbers = [ ...numbers . slice ( 0 , focusedIndex ) , CONST . MAGIC_CODE_EMPTY_CHAR , ...numbers . slice ( focusedIndex + 1 , maxLength ) ] ;
295
- setEditIndex ( focusedIndex ) ;
308
+ editIndex . current = focusedIndex ;
296
309
onChangeTextProp ( composeToString ( numbers ) ) ;
297
310
return ;
298
311
}
@@ -318,17 +331,17 @@ function MagicCodeInput(
318
331
319
332
if ( newFocusedIndex !== undefined ) {
320
333
lastFocusedIndex . current = newFocusedIndex ;
321
- inputRefs . current ?. focus ( ) ;
334
+ inputRef . current ?. focus ( ) ;
322
335
}
323
336
}
324
337
if ( keyValue === 'ArrowLeft' && focusedIndex !== undefined ) {
325
338
const newFocusedIndex = Math . max ( 0 , focusedIndex - 1 ) ;
326
339
setInputAndIndex ( newFocusedIndex ) ;
327
- inputRefs . current ?. focus ( ) ;
340
+ inputRef . current ?. focus ( ) ;
328
341
} else if ( keyValue === 'ArrowRight' && focusedIndex !== undefined ) {
329
342
const newFocusedIndex = Math . min ( focusedIndex + 1 , maxLength - 1 ) ;
330
343
setInputAndIndex ( newFocusedIndex ) ;
331
- inputRefs . current ?. focus ( ) ;
344
+ inputRef . current ?. focus ( ) ;
332
345
} else if ( keyValue === 'Enter' ) {
333
346
// We should prevent users from submitting when it's offline.
334
347
if ( isOffline ) {
@@ -340,7 +353,7 @@ function MagicCodeInput(
340
353
const newFocusedIndex = ( event as unknown as KeyboardEvent ) . shiftKey ? focusedIndex - 1 : focusedIndex + 1 ;
341
354
if ( newFocusedIndex >= 0 && newFocusedIndex < maxLength ) {
342
355
setInputAndIndex ( newFocusedIndex ) ;
343
- inputRefs . current ?. focus ( ) ;
356
+ inputRef . current ?. focus ( ) ;
344
357
if ( event ?. preventDefault ) {
345
358
event . preventDefault ( ) ;
346
359
}
@@ -383,7 +396,7 @@ function MagicCodeInput(
383
396
onLayout = { ( e ) => {
384
397
inputWidth . current = e . nativeEvent . layout . width ;
385
398
} }
386
- ref = { ( inputRef ) => ( inputRefs . current = inputRef ) }
399
+ ref = { ( newRef ) => ( inputRef . current = newRef ) }
387
400
autoFocus = { autoFocus }
388
401
inputMode = "numeric"
389
402
textContentType = "oneTimeCode"
0 commit comments