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

React中基于倒计时子组件状态控制按钮禁用时读取状态失败的问题

解决React父子组件状态同步导致按钮禁用逻辑报错的问题

我来帮你搞定这个问题!你遇到的TypeError: Cannot read property 'seconds' of null报错主要有两个原因:

  • 组件首次渲染时,子组件还没完成挂载,this.child.current还是null,直接访问它的seconds属性自然会报错
  • 直接读取子组件内部的state违反了React的单向数据流设计原则,这不是父子组件通信的正确方式

咱们一步步来修复:

第一步:重构子组件,通过回调传递状态给父组件

修改倒计时子组件,添加一个props回调函数,在倒计时状态变化时主动通知父组件当前的剩余秒数,同时把startTimer从render里移出来(不然每次渲染都会重复启动定时器,导致倒计时混乱):

import React from "react";
import PropTypes from "prop-types";

export default class Counter extends React.Component {
  constructor(props) {
    super(props);
    this.state = { time: {}, seconds: 15 };
    this.timer = 0;
    this.startTimer = this.startTimer.bind(this);
    this.countDown = this.countDown.bind(this);
  }

  // 新增:向父组件传递当前秒数的方法
  notifyParent() {
    this.props.onTimeChange && this.props.onTimeChange(this.state.seconds);
  }

  secondsToTime(secs){
    let hours = Math.floor(secs / (60 * 60));
    let divisor_for_minutes = secs % (60 * 60);
    let minutes = Math.floor(divisor_for_minutes / 60);
    let divisor_for_seconds = divisor_for_minutes % 60;
    let seconds = Math.ceil(divisor_for_seconds);
    let obj = { "h": hours, "m": minutes, "s": seconds };
    return obj;
  }

  componentDidMount() {
    let timeLeftVar = this.secondsToTime(this.state.seconds);
    this.setState({ time: timeLeftVar }, () => {
      // 初始化时通知父组件
      this.notifyParent();
    });
    this.startTimer(); // 在这里启动定时器,而不是render里
  }

  startTimer() {
    if (this.timer === 0 && this.state.seconds > 0) {
      this.timer = setInterval(this.countDown, 1000);
    } else if (this.timer === 0 && this.state.seconds === 0){
      // 注意:不要直接修改state,要用setState
      this.setState({ seconds: 15 }, () => {
        this.timer = setInterval(this.countDown, 1000);
        this.notifyParent();
      });
    }
  }

  countDown() {
    let seconds = this.state.seconds - 1;
    this.setState({
      time: this.secondsToTime(seconds),
      seconds: seconds,
    }, () => {
      // 状态更新后通知父组件
      this.notifyParent();
      if (seconds === 0) {
        clearInterval(this.timer);
        this.timer = 0;
        console.log("counter is 0");
      }
    });
  }

  render() {
    return(
      <span className={ this.state.seconds === 0 ? 'timerHidden' : 'timerActive' }>
        ({this.state.time.s})
      </span>
    );
  }
}

// 定义props类型,确保回调函数是可选的函数类型
Counter.propTypes = {
  onTimeChange: PropTypes.func
};

第二步:修改父组件,通过状态控制按钮禁用

父组件维护自己的状态来控制按钮禁用,通过子组件的回调更新这个状态,同时移除直接访问子组件state的代码:

import Counter from '../Counter/Counter.js';

export default class Verify extends React.Component {
  constructor(props) {
    super(props);
    this.child = React.createRef();
    // 新增:维护按钮禁用状态,初始值根据倒计时初始秒数设置
    this.state = { 
      username: this.username, 
      email: this.email, 
      code: "",
      isButtonDisabled: true // 初始倒计时是15秒,所以按钮默认禁用
    };
  }

  resetTimer = () => {
    this.child.current.startTimer();
  };

  resendConfirmationCode = async e =>{
    this.resetTimer();
    // ... 你的其他逻辑
  }

  // 新增:接收子组件传来的秒数,更新按钮禁用状态
  handleTimeChange = (seconds) => {
    this.setState({
      isButtonDisabled: seconds > 0
    });
  }

  render() {
    return (
      <button 
        className="btn btn-primary register empty" 
        type="button" 
        disabled={this.state.isButtonDisabled} 
        onClick={this.resendConfirmationCode}
      >
        Resend code <Counter ref={this.child} onTimeChange={this.handleTimeChange}/>
      </button>
    );
  }
}

额外注意点

  • 永远不要直接修改子组件的state(比如你之前写的this.state.seconds = 15),必须用setState来更新,否则React无法触发重新渲染
  • 定时器要记得在组件卸载时清理,避免内存泄漏,可以在子组件的componentWillUnmount里添加clearInterval(this.timer)

内容的提问来源于stack exchange,提问作者Eric Mitjans

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.30 04:27:36