You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.05 20:51:01