简体   繁体   English

从两个不同的useEffect调用方法(改变状态)

[英]Calling a method (that mutates state) from two different useEffect

I am playing around with React Hooks, calling a method (that mutates state) from two different useEffect. 我在玩React Hooks,从两个不同的useEffect调用一个方法(改变状态)。 Following code is given: 给出以下代码:

function App() {
  const [clicked, setClicked] = useState(false);

  /**
   * Listen for clicked changes. When clicked changes to true,
   * allow setCounterAndSetUrlHash to do it's thing, before accpeting
   * the next click. So the clicked flag is simply a valve, that opens
   * after two seconds.
   */
  useEffect(() => {
    if (clicked) {
      setCounterAndSetUrlHash(counter + 1);
      setTimeout(() => {
        setClicked(false);
      }, 2000);
    }
  }, [clicked]);

  const [counter, setCounter] = useState(0);

  /**
   * Listen for changes in the URL hash. When the user presses
   * the back button in the browser toolbar, decrement the
   * counter value.
   */
  useEffect(() => {
    window.onhashchange = () => {
      const value = Number(window.location.hash.replace("#", ""));
      // must be number
      if (typeof value === "number" && value % 1 === 0) {
        if (counter - 1 === value) {
          setCounterAndSetUrlHash(counter - 1);
        }
      }
    };
  });

  /**
   * Set a new counter value and apply the same value
   * to the URL hash. I want to reuse this function
   * in both useEffect above.
   */
  const setCounterAndSetUrlHash = value => {
    setCounter(value);
    if (value === 0) {
      window.location.hash = "";
    } else {
      window.location.hash = String(value);
    }
  };

  return (
    <div className="App">
      <p>Clicked: {String(clicked)}</p>
      <p>Counter: {counter}</p>
      <button type="button" onClick={() => setClicked(true)}>
        Click me
      </button>
    </div>
  );
}

The code in action: https://codesandbox.io/s/dreamy-shadow-7xesm 运行中的代码: https//codesandbox.io/s/dreamy-shadow-7xesm

The code is actually working. 该代码实际上正在工作。 However I am getting this warning.. 但是我收到此警告。

React Hook useEffect has a missing dependency: 'counter'. React Hook useEffect缺少依赖项:“计数器”。 Either include it or remove the dependency array. 包括它或删除依赖项数组。 (react-hooks/exhaustive-deps) (反应钩/详尽的下降)

.. and I am not sure how to conform with that while keeping the current functionality. ..而且我不确定在保持当前功能的情况下该如何遵循。 When I add counter to the dependencies, I end up with an infinite loop. 当我添加计数器依赖项时,我会遇到无限循环。

Your first effect uses counter state variable but its dependency list does not include it. 您的第一个效果使用counter状态变量,但其依赖项列表不包括它。 Including it in dependency list will create infinite loop. 将其包含在依赖项列表中将创建无限循环。

You can remove the dependency on counter by using function type argument in setCounter . 您可以通过使用setCounter函数类型参数来消除对counter的依赖。

function App() {
  const [clicked, setClicked] = useState(false);

  /**
   * Listen for clicked changes. When clicked changes to true,
   * allow setCounterAndSetUrlHash to do it's thing, before accpeting
   * the next click. So the clicked flag is simply a valve, that opens
   * after two seconds.
   */
  useEffect(() => {
    if (clicked) {
      incrCounter(1);
      setTimeout(() => {
        setClicked(false);
      }, 2000);
    }
  }, [clicked]);

  const [counter, setCounter] = useState(0);

  /**
   * Listen for changes in the URL hash. When the user presses
   * the back button in the browser toolbar, decrement the
   * counter value.
   */
  useEffect(() => {
    window.onhashchange = () => {
      const value = Number(window.location.hash.replace("#", ""));
      // must be number
      if (typeof value === "number" && value % 1 === 0) {
        if (counter - 1 === value) {
          incrCounter(- 1);
        }
      }
    };
  });

  useEffect(() => {
    if (counter === 0) {
      window.location.hash = "";
    } else {
      window.location.hash = String(counter);
    }
  }, [counter])
  /**
   * Set a new counter value and apply the same value
   * to the URL hash. I want to reuse this function
   * in both useEffect above.
   */
  const incrCounter = delta => {
    setCounter(value => value + delta);
  };

  return (
    <div className="App">
      <p>Clicked: {String(clicked)}</p>
      <p>Counter: {counter}</p>
      <button type="button" onClick={() => setClicked(true)}>
        Click me
      </button>
    </div>
  );
}

Try using the functional setState, setState((state, props) => stateChange) 尝试使用功能性setState, setState((state, props) => stateChange)

useEffect(() => {
  if (clicked) {
    setCounterAndSetUrlHash(counter => counter + 1);
    setTimeout(() => {
      setClicked(false);
    }, 2000);
  }
}, [clicked]);

To solve the issue of the onhashchange callback using the first counter value I suggest to move the functionality to the callback of setCounter . 为了使用第一个计数器值解决onhashchange回调的问题,我建议将功能移到setCounter的回调中。 This would also imply that you need a different function for the button and the hash change. 这也意味着按钮和哈希更改需要其他功能。

Also set the variables and useState definitions at the top, and after useEffect which can make use of them. 还要在顶部以及useEffect之后设置变量和useState定义,以便可以使用它们。 If you want a useEffect to run only once, set an empty array of dependencies; 如果您希望useEffect只运行一次,请设置一个空数组依赖项; leaving out dependecies will run on every render. 忽略依赖项将在每个渲染器上运行。

export const App = () => {
    const [clicked, setClicked] = useState(false);
    const [counter, setCounter] = useState(0);

    /**
     * Listen for clicked changes. When clicked changes to true,
     * allow setCounterAndSetUrlHash to do it's thing, before accpeting
     * the next click. So the clicked flag is simply a valve, that opens
     * after two seconds.
     */
    useEffect(() => {
        if (clicked) {
            setCounter(counter => {
                const value = counter + 1;

                if (value === 0) {
                    window.location.hash = "";
                } else {
                    window.location.hash = String(value);
                }

                return value;
            });

            setTimeout(() => {
                setClicked(false);
            }, 2000);
        }
    }, [clicked]);

    /**
     * Listen for changes in the URL hash. When the user presses
     * the back button in the browser toolbar, decrement the
     * counter value.
     */
    useEffect(() => {
        window.onhashchange = e => {
            const value = Number(window.location.hash.replace("#", ""));

            // must be number
            if (typeof value === "number" && value % 1 === 0) {
                setCounter(counter => {

                    if (counter - 1 !== value) {
                        return counter;
                    }

                    if (value === 0) {
                        window.location.hash = "";
                    } else {
                        window.location.hash = String(value);
                    }

                    return value;
                });
            }
        };
    }, []);

    return (
        <div className="App">
            <p>Clicked: {String(clicked)}</p>
            <p>Counter: {counter}</p>
            <button type="button" onClick={() => setClicked(true)}>
                Click me
            </button>
        </div>
    );
};

Add counter to the first useEffect(): 向第一个useEffect()添加计数器:

  const [counter, setCounter] = useState(0);

  useEffect(() => {
    if (clicked) {
      setCounterAndSetUrlHash(counter + 1);
      setTimeout(() => {
        setClicked(false);
      }, 2000);
    }
  }, [clicked, counter]);

https://codesandbox.io/s/cold-sun-56u7j https://codesandbox.io/s/cold-sun-56u7j

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM