单击链接/按钮导航到其他路线时,ClickAwayListener不会触发

时间:2020-10-26 03:45:31

标签: javascript reactjs material-ui use-effect

我将Material-UI ClickAwayListener组件与使用react-router的现有代码结合使用。该按钮位于<ClickAwayListener> ... </ClickAwayListener>之外,因此我希望onClickAway在导航到其他路线之前会触发。但这不是

下面是我的代码的复制品,在某种程度上证明了我的意思

function Component(){

  const handleClickAway = () => {
    // Do something here
  }

  return (
  <>
    <button>
      <Link to="/other-route">Click here </Link>
    </button>
    // Some other element here
    <ClickAwayListener onClickAway={handleClickAway}>
      <div>
        // Content
      </div>
    </ClickAwayListener>
  </>
  )
}

因此,如果我点击<ClickAwayListener><button>之外的任何位置,就会触发handleClickAway,但是如果我点击<button>,其中包含与其他路线类似的内容不是。

我尝试查看ClickAwayListener的源代码,并且我相信,此part负责检测点击

 React.useEffect(() => {
    if (mouseEvent !== false) {
      const mappedMouseEvent = mapEventPropToEvent(mouseEvent);
      const doc = ownerDocument(nodeRef.current);

      doc.addEventListener(mappedMouseEvent, handleClickAway);

      return () => {
        doc.removeEventListener(mappedMouseEvent, handleClickAway);
      };
    }

    return undefined;
  }, [handleClickAway, mouseEvent]);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.1/umd/react-dom.production.min.js"></script>

据我所知,这部分将首先在安装/重新渲染组件时将事件侦听器添加到click事件,并将在组件卸载之前删除该侦听器(useEffect()的默认行为)。但是,如果是这种情况,那么在涉及到在ClickAwayListener区域之外单击的任何事件导致组件卸载之前,应该触发onClickAway侦听器,因为该侦听器仍附加到click事件上。

简而言之,这是我期望的行为:

单击按钮-> onClickAway射击->卸下组件->转到新路线->运行useEffect()的清理代码->删除侦听器

但这是到目前为止发生的事情

单击按钮->组件卸载->转到新路径->运行useEffect()的清理代码->删除监听器

有人可以帮我解释为什么会这样吗?

3 个答案:

答案 0 :(得分:1)

ClickAwayListener组件通过将事件侦听器附加到document来工作,当触发鼠标事件时,仅当鼠标事件不在元素内时才触发onClickAway

react-router-dom中的Link组件本质上是这样的:

<a onClick={() => navigate()}>click</a>

单击链接并调用navigate()时,React会卸载当前组件,并在下一帧中安装下一页组件。但是事实是,document处理程序仅在下一次重新渲染后进行处理,此时,ClickAwayListener中的事件处理程序由于已卸载而已被删除。 ,所以什么也没叫。

可以通过等到document的处理程序被调用后等待下一次重新渲染来解决问题。

<button
  onClick={() => {
    setTimeout(() => {
      history.push("/2");
    });
  }}
>

实时演示

Edit 64531264/clickawaylistener-doesnt-fire-when-clicking-on-a-link-button-to-navigate-to-oth

答案 1 :(得分:0)

我可以通过使用以下功能代替材料用户界面的ClickAwayListener:https://github.com/streamich/react-use/blob/master/src/useClickAway.ts

解决此问题
import { RefObject, useEffect, useRef } from 'react';
import { off, on } from './util';

const defaultEvents = ['mousedown', 'touchstart'];

const useClickAway = <E extends Event = Event>(
  ref: RefObject<HTMLElement | null>,
  onClickAway: (event: E) => void,
  events: string[] = defaultEvents
) => {
  const savedCallback = useRef(onClickAway);
  useEffect(() => {
    savedCallback.current = onClickAway;
  }, [onClickAway]);
  useEffect(() => {
    const handler = (event) => {
      const { current: el } = ref;
      el && !el.contains(event.target) && savedCallback.current(event);
    };
    for (const eventName of events) {
      on(document, eventName, handler);
    }
    return () => {
      for (const eventName of events) {
        off(document, eventName, handler);
      }
    };
  }, [events, ref]);
};

export default useClickAway;

我不想将库作为依赖项导入,所以我创建了此代码的钩子并将util函数复制到其中

const on = (obj: any, ...args: any[]) => obj.addEventListener(...args);
const off = (obj: any, ...args: any[]) => obj.removeEventListener(...args);

此处提供了一个使用中的钩子示例:https://streamich.github.io/react-use/?path=/story/ui-useclickaway--demo

答案 2 :(得分:0)

与您的问题相关的issue已于2019年9月在Material UI repository中打开,其中Clickawaylistener不会同时激活按钮和链接。但是,直到2020年5月,这个问题似乎仍未得到充分解决。我建议在存储库中打开一个新问题,以确认这不是与按钮相关的错误。

同时,作为一种潜在的解决方法,根据您的用例,您可以将<Link><Button>组件与onClick道具一起使用:

import React from "react";
import ClickAwayListener from "@material-ui/core/ClickAwayListener";
import { Link, useHistory } from "react-router-dom";
import Button from "@material-ui/core/Button";

export default function Home() {
  let history = useHistory();

  const handleClickAway = () => {
    console.log("Clicked Away");
  };

  function handleClick() {
    handleClickAway();
    history.push("/about");
  }

  return (
    <div>
      <Link to="/about" onClick={handleClickAway}>
        Link
      </Link>
      <Button type="button" onClick={handleClick}>
        Button
      </Button>
      <ClickAwayListener onClickAway={handleClickAway}>
        <div>Home Page</div>
      </ClickAwayListener>
    </div>
  );
}

我还在Codesandbox上包含了一个工作示例。

 Working Example