React Native OTP输入框更新state后无法切换到下一个TextInput焦点问题
问题根因
React的状态更新是异步批量执行的,你在调用setOtp后会触发组件重新渲染,而你的OtpInput是直接定义在OtpValidate组件内部的,每次组件重渲染都会重新生成一个全新的OtpInput实例,之前存在textInputRef里的旧输入框引用就会失效,所以后续执行focus()操作时找不到正确的输入框元素,自然无法跳转焦点。
方法1:调整组件定义位置(推荐)
把OtpInput组件移到OtpValidate外部,通过props传递需要的参数,避免每次重渲染重建组件,修改后的代码如下:
import React, { useState, useEffect, useRef } from 'react'; import { View, Text, TextInput, Button, TouchableOpacity, StyleSheet, } from 'react-native'; import { Grid, Col, Row } from 'react-native-easy-grid'; // 移到外部定义 const OtpInput = ({ otp, textInputRef, changeFocus }) => { return ['textInput1', 'textInput2', 'textInput3', 'textInput4'].map( (val, index) => ( <Col key={index}> <TextInput ref={textInputRef[index]} style={styles.OtpInput} maxLength={1} keyboardType="numeric" value={otp[index]} autoFocus={index === 0 ? true : undefined} onChangeText={(value) => changeFocus(value, index)} /> </Col> ) ); }; export default function App() { return ( <OtpValidate/> ); } function OtpValidate() { const [otp, setOtp] = useState([]); const [resendButtonDisableTime, setResendButtonDisableTime] = useState(6); const textInputRef = [useRef(), useRef(), useRef(), useRef()]; useEffect(() => { timer(); }, [resendButtonDisableTime]); function timer() { const timerObject = setTimeout(() => { if (resendButtonDisableTime < 0) clearTimeout(timerObject); else setResendButtonDisableTime(resendButtonDisableTime - 1); }, 1000); } function changeFocus(value, index) { if (value.length == 1) { const otpCopy = otp.concat(); otpCopy[index] = value; setOtp(otpCopy); // 现在ref不会失效,可以直接focus,加边界判断避免越界报错 if (index < 3) textInputRef[++index].current.focus(); } else { const otpCopy = [...otp]; otpCopy[index] = null; setOtp(otpCopy); if (index > 0) textInputRef[--index].current.focus(); } } return ( <Grid> <Row size={20} style={{ alignItems: 'center', paddingLeft: 10 }}> <Text>{'Enter the otp recieved in the device'}</Text> </Row> <Row size={30}> {/* 传递props */} <OtpInput otp={otp} textInputRef={textInputRef} changeFocus={changeFocus} /> </Row> <Row size={10} style={{ paddingLeft: 10 }}> <View> {resendButtonDisableTime > 0 ? ( <Text>{'Resend otp in ' + resendButtonDisableTime + ' sec'}</Text> ) : ( <TouchableOpacity> <Text>{'Resend'}</Text> </TouchableOpacity> )} </View> </Row> <Row size={40} style={{ marginHorizontal: 10 }}> <Col> <Button title="submit" /> </Col> </Row> </Grid> ); } const styles = StyleSheet.create({ OtpInput: { margin: 10, padding: 10, borderWidth: 1, }, });
方法2:延迟执行焦点切换(快速修复)
如果不想调整组件结构,可以把焦点切换操作放到setTimeout中,等状态更新、组件重渲染完成后再执行,仅修改changeFocus方法即可:
function changeFocus(value, index) { if (value.length == 1) { const otpCopy = otp.concat(); otpCopy[index] = value; setOtp(otpCopy); // 加延迟,等重渲染完成后再执行focus,加边界判断避免越界报错 setTimeout(() => { if (index < 3) textInputRef[++index].current.focus(); }, 0) } else { const otpCopy = [...otp]; otpCopy[index] = null; setOtp(otpCopy); setTimeout(() => { if (index > 0) textInputRef[--index].current.focus(); }, 0) } }
内容的提问来源于stack exchange,提问作者sandesh
相关产品推荐
相关产品推荐

