简体   繁体   English

在 React 中使用 Hooks 设置点击事件的时间间隔不会使用更新的 state 值

[英]Setting an interval on a click event in React using Hooks won't use updated state values

I am trying to create a filter dropdown menu that hides itself if the user did not close it manually and has moved their mouse away from the filter after 1 second.我正在尝试创建一个过滤器下拉菜单,如果用户没有手动关闭它并在 1 秒后将鼠标从过滤器上移开,它会自行隐藏。

The problem is that the mouseInside boolean state never gets changed to false inside the interval.问题是mouseInside boolean state 在间隔内永远不会更改为false I believe this is because the react component gets re-rendered but not entirely sure.我相信这是因为反应组件被重新渲染但并不完全确定。

I don't know how to fix it or how I might be using state vs local variables incorrectly.我不知道如何修复它,或者我可能如何错误地使用 state 与局部变量。

I'm using TypeScript in React with hooks:我在带有钩子的 React 中使用 TypeScript:

const Filter: React.FC = () => {
  const [shown, setShown] = useState<boolean>(false);
  const [mouseInside, setMouseInside] = useState<boolean>(false);
  
  let interval: NodeJS.Timeout | null = null;

  const handleToggle = () => {
      setShown(!shown);
      
      interval = setInterval(() => {
        console.log(mouseInside);

        // mouseInside is permanently true
        if (!mouseInside && interval) {
           // this never gets triggered
           clearInterval(interval);
           setShown(false);
           console.log("Finished!");
        }
      }, 1000);
    }
  };

  return (
    <div
      style={{ width: 100, height: 100, backgroundColor: "lightgray" }}  
      onClick={handleToggle}
      onMouseEnter={() => setMouseInside(true)}
      onMouseLeave={() => setMouseInside(false)}
    >
        <p>Click me!</p>
        
        {shown && <div>drop down here...</dv>}
    </div>
    );
};

I need mouseInside to eventually change to false when the user leaves the filter box.当用户离开过滤器框时,我需要 mouseInside 最终更改为 false。 The onMouseLeave event is definitely being triggered but it's the React side of things I think is causing the issue. onMouseLeave事件肯定会被触发,但我认为这是导致问题的事情的 React 方面。

Maybe I need to use useEffect ?也许我需要使用useEffect I don't know the best approach though on how to use this with setInterval .我不知道如何将它与setInterval一起使用的最佳方法。

In order to "subscribe" a callback function to a change in state, you can definitely use the useEffect hook:为了“订阅”一个回调 function 到 state 的变化,你绝对可以使用useEffect钩子:

function automaticallySetMouseOutside() {  
  setTimeout(() => {
    if (mouseInside) {
      setMouseInside(false);
    }
  }, 1000);
}

useEffect(automaticallySetMouseOutside, [mouseInside]);

useEffect 's second argument subscribes automaticallySetMouseOutside to get called whenever mouseInside is changed. useEffect的第二个参数automaticallySetMouseOutside订阅 SetMouseOutside 以在mouseInside更改时被调用。

While this code is meant to exemplify the use of this hook, it's not accurate - as it can close the menu while the user is still inside.虽然这段代码是为了举例说明这个钩子的使用,但它并不准确——因为它可以在用户还在里面时关闭菜单。
If you want a full solution to your issue, please check this CodeSandBox .如果您想为您的问题提供完整的解决方案,请查看此CodeSandBox

While declaring the interval action inside handleToggle , it lives inside its own scope, with no binding to the actual state,handleToggle中声明间隔操作时,它存在于自己的 scope 中,与实际的 state 没有绑定,

const handleInterval = interval => { 
        console.log(mouseInside);
        if (!mouseInside && interval) {
           // this never gets triggered
           clearInterval(interval);
           setShown(false);
           console.log("Finished!");
        }
      }
   
const handleToggle = () => {
      interval = setInterval(() => {
        () => handleInterval(interval),
      }, 1000);
    }
  };

I ended up using part of what @GalAbra suggested but I wanted to only create the interval when the user clicks on the toggler and only if the menu was open:我最终使用了@GalAbra 建议的部分内容,但我只想在用户单击切换器并且仅在菜单打开时创建间隔:

  const [shown, setShown] = useState<boolean>(false);
  const [mouseInside, setMouseInside] = useState<boolean>(false);

  useEffect(() => {
    let interval: NodeJS.Timeout | null;

    if (shown) {
      interval = setInterval(() => {
        if (!mouseInside && interval) {
          setShown(false);
        }
      }, 500);
    }

    return () => {
      if (interval) clearInterval(interval);
    };
  }, [shown, mouseInside]);

  return (
    <div
      style={{ width: 100, height: 100, backgroundColor: "lightgray" }}  
      onClick={() => setShown(!shown)}
      onMouseEnter={() => setMouseInside(true)}
      onMouseLeave={() => setMouseInside(false)}
    >
        <p>Click me!</p>        
        {shown && <div>drop down here...</dv>}
    </div>
  );

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

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