Установка интервала для события клика в React с использованием хуков не будет использовать обновленные значения состояния - PullRequest
0 голосов
/ 20 июня 2020

Я пытаюсь создать раскрывающееся меню фильтра, которое скрывается, если пользователь не закрыл его вручную и отодвинул свою мышь от фильтра через 1 секунду.

Проблема в том, что mouseInside логическое состояние никогда не изменяется на false внутри интервала. Я считаю, что это связано с тем, что компонент реакции повторно отображается, но не совсем уверен.

Я не знаю, как это исправить или как я могу неправильно использовать состояние и локальные переменные. Я использую TypeScript в React с хуками:

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>
    );
};

Мне нужно, чтобы mouseInside в конечном итоге изменилась на false, когда пользователь покидает поле фильтра. Событие onMouseLeave определенно запускается, но я думаю, что проблема связана с реакцией.

Может быть, мне нужно использовать useEffect? Я не знаю лучшего подхода, как использовать это с setInterval.

Ответы [ 3 ]

1 голос
/ 20 июня 2020

Чтобы «подписать» функцию обратного вызова на изменение состояния, вы можете определенно использовать ловушку useEffect:

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

useEffect(automaticallySetMouseOutside, [mouseInside]);
*

useEffect второй аргумент подписывает automaticallySetMouseOutside, чтобы получить вызывается всякий раз, когда изменяется mouseInside.

Хотя этот код предназначен для иллюстрации использования этого хука, он неточен - так как он может закрыть меню, пока пользователь все еще находится внутри. Если вам нужно полное решение вашей проблемы, проверьте это CodeSandBox .

0 голосов
/ 20 июня 2020

В итоге я использовал часть того, что предлагал @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>
  );
0 голосов
/ 20 июня 2020

При объявлении действия интервала внутри handleToggle, он живет внутри своей собственной области, без привязки к фактическому состоянию,

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);
    }
  };
...