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

React中Main组件定时更新数据导致Modal计时器异常的问题排查

Fixing Your React Timer Glitch: Why the Modal Timer Speeds Up and Hits -1

Hey Vlad, let's dig into why your timers are misbehaving and get them working right. I spot a couple of key issues in both your Main and Modal components that are causing the chaos.

What's Going Wrong?

1. Main Component: Misusing Lifecycle Methods & Leaking Timers

  • You're manually calling componentWillMount() in your setInterval—this is a big no-no. Lifecycle methods are meant to be called by React, not your code. Every time you run componentWillMount(), it triggers a setState, which re-renders the Main component. This re-render triggers componentDidUpdate in the Modal even if the isOpened prop hasn't changed.
  • Your componentWillUnmount only sets this.timer = null—it doesn't actually clear the interval with clearInterval(). This leaves the interval running in the background, causing endless re-renders and memory leaks.

2. Modal Component: Overlapping Timers & Stale State

  • In componentDidUpdate, you're creating a new setTimeout every time the component updates (including when Main re-renders due to its interval). This means multiple timers are running at the same time, making the counter tick down faster than 1 second per step.
  • You're using this.state.counter directly in setState, which can lead to stale state values (since setState is asynchronous). When multiple timers fire, they might all use the same old counter value, leading to unexpected drops like going from 1 to -1.
  • You don't clear the timer when the counter hits 0, so the timer keeps running and tries to decrement a counter that's already at 0.

The Fixes

Step 1: Fix the Main Component's Data Fetching & Timer

First, let's clean up the Main component by separating data fetching into a dedicated method, fixing the interval cleanup, and ditching the manual lifecycle method call:

import React, { Component } from 'react';

class Main extends Component {
 constructor(props) {
 super(props);
 this.state = { data: [], isOpened: false };
 this.submitForm = this.submitForm.bind(this);
 this.fetchData = this.fetchData.bind(this); // Bind the fetch method
 }

 // Separate data fetching into its own method
 fetchData() {
 fetch('/api/global.json')
 .then(response => response.json())
 .then(result => this.setState({ data: result.data }));
 }

 componentDidMount() {
 this.fetchData(); // Initial fetch
 this.interval = setInterval(this.fetchData, 10000); // Call the dedicated method, not a lifecycle hook
 }

 componentWillUnmount() {
 clearInterval(this.interval); // Actually clear the interval to prevent leaks
 }

 changeModal = () => {
 this.setState({ isOpened: !this.state.isOpened });
 }

 submitForm(e) {
 e.preventDefault();
 this.setState({ isOpened: !this.state.isOpened });
 }

 render() {
 return (
 <div>
 <form onSubmit={this.submitForm}>
 <button className="button button-main" type="submit">Modal open</button>
 </form>
 <Modal isOpened={this.state.isOpened} changeModal={this.changeModal} />
 </div>
 );
 }
}

Step 2: Fix the Modal Component's Timer Logic

Now let's fix the Modal timer to prevent overlapping timers, use safe state updates, and clean up properly:

class Modal extends Component {
 constructor(props) {
 super(props);
 this.state = { counter: 30 };
 this.timer = null;
 }

 componentDidUpdate(prevProps) {
 // Only handle timer logic when isOpened changes
 if (prevProps.isOpened !== this.props.isOpened) {
 if (this.props.isOpened) {
 // Start timer when modal opens
 this.startTimer();
 } else {
 // Reset and clear timer when modal closes
 clearTimeout(this.timer);
 this.setState({ counter: 30 });
 }
 }
 }

 startTimer() {
 // Clear any existing timer first to avoid overlaps
 if (this.timer) clearTimeout(this.timer);

 if (this.state.counter > 0) {
 this.timer = setTimeout(() => {
 // Use functional setState to get the latest state value
 this.setState(prevState => ({ counter: prevState.counter - 1 }), () => {
 // Recursively start the next timer only if counter is still > 0
 if (this.state.counter > 0) {
 this.startTimer();
 }
 });
 }, 1000);
 }
 }

 componentWillUnmount() {
 clearTimeout(this.timer); // Clean up timer when component unmounts
 }

 render() {
 const padTime = time => {
 return String(time).length === 1 ? `0${time}` : `${time}`;
 };

 const format = time => {
 const minutes = Math.floor(time / 60);
 const seconds = time % 60;
 return `${minutes} мин ${padTime(seconds)} сек`;
 };

 return (
 <div className={"modal-overlay" + (this.props.isOpened ? " open" : " close")}>
 <div className='modal-content'>
 <div className='modal-close' onClick={this.props.changeModal}>×</div>
 <div className="modal-note">
 {this.state.counter === 0 ? 'Finish!' : `Timer ${format(this.state.counter)}`}
 </div>
 </div>
 </div>
 )
 }
}

Key Improvements Explained

  • No manual lifecycle calls: We use a dedicated fetchData method instead of calling componentWillMount directly, preventing unnecessary re-renders.
  • Proper timer cleanup: Both components clear their timers in componentWillUnmount to avoid memory leaks and leftover timers.
  • Functional setState: Using prevState ensures we always have the latest counter value, preventing stale state bugs.
  • Controlled timer flow: The modal's timer is only started when the modal opens, and we clear existing timers before starting new ones to avoid overlaps.
  • Stop timer at 0: The timer stops automatically once the counter reaches 0, so it won't decrement to -1.

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.29 21:23:09