【问题标题】:set interval not updating the variable设置间隔不更新变量
【发布时间】:2021-04-29 08:08:22
【问题描述】:

我正在尝试捕获用户的点击次数。 我希望每 15 分钟发送一次 api, 我在 useEffect 中使用 setinterval 来实现这一点,但问题是即使状态在外部发生变化,但在 setinterval 内部却没有。 setinterval 只是给出初始默认值。

这里是代码 -

const Agent = (props: RouteComponentProps) => {
  const [clicks, setClicks] = useState(0);

  const handleOnIdle = () => {
    console.log("user is idle");
    console.log("last active", new Date(getLastActiveTime()));
    console.log("total idle time: ", getTotalIdleTime() / 1000);
  };

  const handleOnActive = () => {
    console.log("user is active");
    console.log("time remaining", getRemainingTime());
  };

  const handleOnAction = () => {
    console.log("user did something", clicks);
    setClicks(clicks + 1);
  };

  const {
    getRemainingTime,
    getLastActiveTime,
    getTotalIdleTime,
  } = useIdleTimer({
    timeout: 10000,
    onIdle: handleOnIdle,
    onActive: handleOnActive,
    onAction: handleOnAction,
    debounce: 500,
  });

  useEffect(() => {
    let timer = setInterval(
      () => alert(`user clicked ${clicks} times`),
      1000 * 30
    );

    return () => {
      clearInterval(timer);
      setClicks(0);
    };
  }, []);

  return (
    <AgentLayout>
      <div className="dashboard-wrapper py-3">
        <Switch>
          <Redirect
            exact
            from={`${props.match.url}/`}
            to={`${props.match.url}/home`}
          />
          <Route path={`${props.match.url}/home`} component={Home} />
          <Route
            path={`${props.match.url}/lead-details`}
            component={leadDetails}
          />
          <Route
            path={`${props.match.url}/fill-details`}
            component={FillDetails}
          />
          <Route
            path={`${props.match.url}/my-desktime`}
            component={MyDesktime}
          />

          <Redirect to="/error" />
        </Switch>
      </div>
    </AgentLayout>

警报提示 用户点击了 0 次

【问题讨论】:

    标签: javascript reactjs react-hooks setinterval


    【解决方案1】:

    问题是定时器函数只使用了clicks第一个 版本,因为它在第一次调用组件函数时关闭了该版本。当你将一个空的依赖数组传递给useEffect时,useEffect回调只被调用一次,第一次调用组件函数时(挂载组件时)。

    您可以通过让计时器函数改用setClicks 方法来解决此问题:

    useEffect(() => {
        let timer = setInterval(
            () => {
                setClicks(currentClicks => {                      // ***
                    alert(`user clicked ${currentClicks} times`); // ***
                    return currentClicks;                         // ***
                });                                               // ***
            },
            1000 * 30
        );
    
        return () => {
            clearInterval(timer);
            // setClicks(0); // <=== Don't do this, the component is unmounting
        };
    }, []);
    

    现在,计时器使用回调形式调用setClicks,这意味着它接收到clicks 的当前值。因为它返回相同的值,所以它不会更新组件的状态。

    也可以通过添加clicks 作为useEffect 的依赖项来解决这个问题,但它有点复杂。天真的方法就是这样做:

    // The naive way
    useEffect(() => {
        let timer = setInterval(
            () => {
                alert(`user clicked ${clicks} times`);
            },
            1000 * 30
        );
    
        return () => {
            clearInterval(timer);
            // setClicks(0); // <=== Don't do this, the user's click count would get reset every time
        };
    }, [clicks]);
    //  ^^^^^^−−−−−−−−−−−−− ***
    

    这将大部分工作,但它会在每次clicks 更改时重新启动计时器,即使这意味着它不是等待 30 秒,而是等待 45 秒(因为 clicks 在 15 秒后更改,这取消了之前的间隔并重新开始)。对于非常短的间隔,这可能无关紧要,但对于 30 秒的间隔,它似乎不太理想。在不弄乱时间间隔的情况下这样做需要您记住下一次计时器回调应该发生的时间并调整初始延迟的持续时间以匹配,这变得相当复杂。


    我之前没有注意到,但是任何时候根据现有状态更新状态,我建议使用回调表单。所以你的

    const handleOnAction = () => {
        setClicks(clicks => clicks + 1); // Note the function callback
    };
    

    【讨论】:

    • @MohammadUbaid - 很高兴有帮助!您还应该删除清理函数中的setClicks(0); 调用,我已经更新以显示这一点。在我们将[] 传递给useEffect 的版本中,在卸载组件时会调用清理函数,因此您不应设置状态。在我们传递[clicks] 的版本中,每次clicks 更改时都会调用清理函数,所以我们真的不应该在那里更新clicks。 :-D
    • @t-j-crowder 我还有一个问题。介意看看吗?
    • @MohammadUbaid - 我现在很忙,但也许以后。你发了吗?
    • @t-j-crowder 是的,刚刚。
    【解决方案2】:

    问题

    1. 您有一个陈旧的外壳 @9​​87654321@ 状态,在安装效果运行并开始间隔时从初始渲染周期关闭。
    2. 您的点击更新程序应该使用功能更新来更新之前状态的clicks 计数。这涵盖了用户是否能够以某种方式比反应组件生命周期更快地单击并在渲染周期中将多个 clicks 更新排入队列。

    T.J. 的回答很好,但我认为 useState 更新程序函数是一个纯函数,而中间的 alert(`user clicked ${currentClicks} times`); 是副作用,应该避免(恕我直言)。

    解决方案

    更新 handleOnAction 以使用功能状态更新。

    const handleOnAction = () => {
      console.log("user did something", clicks);
      setClicks(clicks => clicks + 1);
    };
    

    我建议使用 React ref 和额外的 useEffect 来更新它,以保存 clicks 状态的缓存副本以供间隔回调参考。

    const clicksRef = useRef();
    
    useEffect(() => {
      clicksRef.current = clicks; // update clicks ref when clicks updates
    }, [clicks]);
    
    useEffect(() => {
      const timer = setInterval(
        () => alert(`user clicked ${clicksRef.current} times`), // use clicks ref instead
        1000 * 30
      );
    
      return () => {
        clearInterval(timer);
        // TJ already covered removing this extra state update
      };
    }, []);
    

    【讨论】:

      猜你喜欢
      • 2020-06-21
      • 2017-10-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-08-28
      • 1970-01-01
      • 2022-12-05
      • 1970-01-01
      相关资源
      最近更新 更多