反应状态未按预期更新

时间:2020-10-27 16:31:11

标签: javascript reactjs react-hooks

我有一个组件,当它可见时,我想每10秒查询一次API。当组件关闭/不可见时,我希望该轮询停止。

我的问题是,即使隐藏了组件,轮询仍会继续。无论我做什么,我的代码都无法识别新近更新的状态变量。

const DisplayState = Object.freeze({
    DisplayGroupNamesForSelection: 0,
    DisplayLogStream: 1
});

export default function LogStream({ isOpen }) {
    const [currentState, setCurrentState] = useState(DisplayState.DisplayGroupNamesForSelection);
    const [logEvents, setlogEvents] = useState([]);

    const onStartStreamingButtonClicked = async logGroupName => {
        setCurrentState(DisplayState.DisplayLogStream);
        pollForLogs(logGroupName);
    };

    const pollForLogs = async logGroupName => {
        try {
            const newLogList = await getLogs(logGroupName); // API call elsehwere
            setlogEvents(newLogList);
        } catch(e) {
            //
        } finally {
            // If the component is still open, ping the API again in 10s
            // However, this always evaluates to true
            if(isOpen) {
                setTimeout(() => {
                    pollForLogs(logGroupName);
                }, 10000);
            }
        }
    };

    return (
        <>
            {!isOpen ? (
                <></>
            ) : (
                <div>
                    {(() => {
                        switch(currentState) {
                            case DisplayState.DisplayGroupNamesForSelection: {
                                return (
                                    <LogStreamingGroupSelector
                                        onStartStreamingButtonClicked={onStartStreamingButtonClicked}
                                    />
                                );
                            }
                            case DisplayState.DisplayLogStream: {
                                return (
                                    <div>
                                        {logEvents.map((log, i) => (
                                            <sub key={log.timestamp}>{log.message}</sub>
                                        ))}
                                    </div>
                                );
                            }
                        }
                    })()}
                </div>
            )}
        </>
    );
}

我所能做的是一种有点棘手的解决方案,其中我在组件外部声明了一个变量,然后继续轮询取决于该变量:

let continuePollingForLogs = false;

export default function LogStream({ isOpen }) {
    const [currentState, setCurrentState] = useState(DisplayState.DisplayGroupNamesForSelection);
    const [logEvents, setlogEvents] = useState([]);

    useEffect(() => {
        continuePollingForLogs = isOpen;
    }, [isOpen]);

...然后,如果我有pollForLogs函数依赖于非状态变量continuePollingForLogs,则它的行为确实符合预期。

我知道React在处理状态方面出了点问题,但我到底不能弄清楚什么。即使我创建了独立的useState变量,但无论何时useEffect进行更改,我都在isOpen内进行了设置,该变量仍然不起作用。 pollForLogs()的{​​{1}}中的变量始终为finally

2 个答案:

答案 0 :(得分:2)

您可以使用useRef创建一个可变的ref(变量),其状态(值)将在组件的整个生命周期中保持不变。然后,在卸载组件时,使用useEffect中的cleanup函数将其设置为false。

import React, { useState, useRef } from 'react';

export default function LogStream({ isOpen }) {
  const [currentState, setCurrentState] = useState(
    DisplayState.DisplayGroupNamesForSelection
  );
  const [logEvents, setlogEvents] = useState([]);

  const continuePollingForLogs = useRef(isOpen);

  useEffect(() => {
    return () => {
      // set ref to false when component unmounts.
      continuePollingForLogs.current = false;
    };
  });
}

答案 1 :(得分:1)

这是使用钩子时的错误方法。我建议任何将来的读者阅读Dan Abramov的文章,并使用其useInterval自定义钩子:

https://overreacted.io/making-setinterval-declarative-with-react-hooks/