【问题标题】:Why is React not updating state and display in real-time?为什么 React 没有实时更新状态和显示?
【发布时间】:2025-12-02 17:45:01
【问题描述】:

我无法让我的 react 项目实时更新“timeRemaining”状态。我知道它是异步运行的,但我不知道如何解决这个问题。该项目按其应有的方式工作,除了“timeRemaining”应在应单击后一键更新。例如,它呈现: (开始时间:25:00)点击=> (应该是:20:00,实际上是:25:00)点击=> (应该是:15:00,实际上是:20:00)......等等......我该如何处理这个延迟?

“timeRemaining”状态由“handleFocusTimeAdd/sub”和“handleBreakTimeAdd/Sub”函数更新,并由“infoComponent”显示。

以下是全部代码:

import React, { useState } from "react";
import classNames from "../utils/class-names";
import useInterval from "../utils/useInterval";
import { minutesToDuration, secondsToDuration } from "../utils/duration";

function Pomodoro() {
  // Timer starts out paused
  const [isTimerRunning, setIsTimerRunning] = useState(false);
  const [focusDuration, setFocusDuration] = useState(1500);
  const [breakDuration, setBreakDuration] = useState(300);
  const [timeRemaining, setTimeRemaining] = useState(focusDuration);
  const [onBreak, setOnBreak] = useState(false);
  const [showInfo, setShowInfo] = useState(false);

  // Update every second if timer is running
  useInterval(
    () => {
      setTimeRemaining((timeRemaining) => timeRemaining - 1);
      play();
    },
    isTimerRunning ? 1000 : null
  );

  // Handle the PLAY button
  function playPause() {
    setIsTimerRunning((prevState) => !prevState);
    setShowInfo(true);
  }

  // Handle PLAY
  function play() {
    if (timeRemaining === 0) {
      if (onBreak) {
        setTimeRemaining((timeRemaining) => timeRemaining + focusDuration);
        setOnBreak(false);
      }
      if (!onBreak) {
        setTimeRemaining((timeRemaining) => timeRemaining + breakDuration);
        setOnBreak(true);
      }
    }
  }

  // Handle the STOP button
  const handleStopBtn = () => {
    setIsTimerRunning(false);
    setFocusDuration(1500);
    setBreakDuration(300);
    setTimeRemaining(1500);
    setShowInfo(false);
  };

  // Handle the focus duration view SUBTRACT
  const handleFocusTimeSub = (focusDuration) => {
    if (focusDuration > 300) {
      setFocusDuration((focusDuration) => focusDuration - 300);
    } else {
      setFocusDuration((focusDuration) => focusDuration);
    }
    setTimeRemaining(focusDuration)
    return focusDuration;
  };

  // Handle the focus duration view ADD
  const handleFocusTimeAdd = (focusDuration) => {
    if (focusDuration < 3600) {
      setFocusDuration((focusDuration) => focusDuration + 300);
    } else {
      setFocusDuration((focusDuration) => focusDuration);
    }
    setTimeRemaining(focusDuration)
    return focusDuration;
  };

  // Handle the break duration view SUBTRACT
  const handleBreakTimeSub = (breakDuration) => {
    if (breakDuration > 60) {
      setBreakDuration((breakDuration) => breakDuration - 60);
    } else {
      setBreakDuration((breakDuration) => breakDuration);
    }
    return breakDuration;
  };

  // Handle the break duration view ADD
  const handleBreakTimeAdd = (breakDuration) => {
    if (breakDuration < 900) {
      setBreakDuration((breakDuration) => breakDuration + 60);
    } else {
      setBreakDuration((breakDuration) => breakDuration);
    }
    return breakDuration;
  };

  const infoComponent = () => {
    if (showInfo) {
      if (onBreak) {
        return (
          <div>
            <div className="row mb-2">
              <div className="col">
                <h2 data-testid="session-title">
                  On Break for {secondsToDuration(breakDuration)} minutes
                </h2>
                <p className="lead" data-testid="session-sub-title">
                  {secondsToDuration(timeRemaining)} remaining
                </p>
              </div>
            </div>
            <div className="row mb-2">
              <div className="col">
                <div className="progress" style={{ height: "20px" }}>
                  <div
                    className="progress-bar"
                    role="progressbar"
                    aria-valuemin="0"
                    aria-valuemax="100"
                    aria-valuenow="0" // TODO: Increase aria-valuenow as elapsed time increases
                    style={{ width: "0%" }} // TODO: Increase width % as elapsed time increases
                  />
                </div>
              </div>
            </div>
          </div>
        );
      }
      if (!onBreak) {
        return (
          <div>
            <div className="row mb-2">
              <div className="col">
                <h2 data-testid="session-title">
                  Focusing for {secondsToDuration(focusDuration)} minutes
                </h2>
                <p className="lead" data-testid="session-sub-title">
                  {secondsToDuration(timeRemaining)} remaining
                </p>
              </div>
            </div>
            <div className="row mb-2">
              <div className="col">
                <div className="progress" style={{ height: "20px" }}>
                  <div
                    className="progress-bar"
                    role="progressbar"
                    aria-valuemin="0"
                    aria-valuemax="100"
                    aria-valuenow="0" // TODO: Increase aria-valuenow as elapsed time increases
                    style={{ width: "0%" }} // TODO: Increase width % as elapsed time increases
                  />
                </div>
              </div>
            </div>
          </div>
        );
      }
    }
    return null;
  };

  return (
    <div className="pomodoro">
      <div className="row">
        <div className="col">
          <div className="input-group input-group-lg mb-2">
            <span className="input-group-text" data-testid="duration-focus">
              Focus Duration: {secondsToDuration(focusDuration)}
            </span>
            <div className="input-group-append">
              <button
                onClick={() => handleFocusTimeSub(focusDuration)}
                type="button"
                className="btn btn-secondary"
                data-testid="decrease-focus"
              >
                <span className="oi oi-minus" />
              </button>

              <button
                onClick={() => {
                  handleFocusTimeAdd(focusDuration);
                }}
                type="button"
                className="btn btn-secondary"
                data-testid="increase-focus"
              >
                <span className="oi oi-plus" />
              </button>
            </div>
          </div>
        </div>
        <div className="col">
          <div className="float-right">
            <div className="input-group input-group-lg mb-2">
              <span className="input-group-text" data-testid="duration-break">
                Break Duration: {secondsToDuration(breakDuration)}
              </span>
              <div className="input-group-append">
                <button
                  onClick={() => handleBreakTimeSub(breakDuration)}
                  type="button"
                  className="btn btn-secondary"
                  data-testid="decrease-break"
                >
                  <span className="oi oi-minus" />
                </button>
                <button
                  onClick={() => handleBreakTimeAdd(breakDuration)}
                  type="button"
                  className="btn btn-secondary"
                  data-testid="increase-break"
                >
                  <span className="oi oi-plus" />
                </button>
              </div>
            </div>
          </div>
        </div>
      </div>
      <div className="row">
        <div className="col">
          <div
            className="btn-group btn-group-lg mb-2"
            role="group"
            aria-label="Timer controls"
          >
            <button
              type="button"
              className="btn btn-primary"
              data-testid="play-pause"
              title="Start or pause timer"
              onClick={playPause}
            >
              <span
                className={classNames({
                  oi: true,
                  "oi-media-play": !isTimerRunning,
                  "oi-media-pause": isTimerRunning,
                })}
              />
            </button>
            <button
              onClick={handleStopBtn}
              type="button"
              className="btn btn-secondary"
              title="Stop the session"
            >
              <span className="oi oi-media-stop" />
            </button>
          </div>
        </div>
      </div>
      <div>{infoComponent()}</div>
    </div>
  );
}

export default Pomodoro;

感谢您的帮助!我是 React 新手,仍在解决问题。

【问题讨论】:

    标签: javascript reactjs asynchronous onclick use-state


    【解决方案1】:

    您似乎在回调中访问了对某个值的错误引用。我将重命名您的 handleFocusTimeSub 回调中的变量以暴露错误。

    const handleFocusTimeSub = (inputFocusDuration) => {
      if (inputFocusDuration > 300) {
        setFocusDuration((stateFocusDuration) => stateFocusDuration - 300);
      } else {
        setFocusDuration((stateFocusDuration) => stateFocusDuration);
      }
      setTimeRemaining(inputFocusDuration);
      return inputFocusDuration;
    };
    

    您可能打算将 focusDuration 的新状态传递给 setTimeRemaining

    const handleFocusTimeSub = (inputFocusDuration) => {
      if (inputFocusDuration > 300) {
        setFocusDuration(
          (stateFocusDuration) => (
            setTimeRemaining(stateFocusDuration - 300),
            stateFocusDuration - 300
          )
        );
      } else {
        setFocusDuration(
          (stateFocusDuration) => (
            setTimeRemaining(stateFocusDuration),
            stateFocusDuration
          )
        );
      }
      return inputFocusDuration;
    };
    

    handleFocusTimeAdd 有同样的错误,修复也是一样的。

    我向您展示的修复是使其工作的最小更改。为避免将来出现此类错误,您需要将需要相互了解的状态变量保存在共享的 reducer 中,而不是单独的状态挂钩中。

    【讨论】:

      最近更新 更多