盖茨比:事件监听器无法在React useEffects钩子上卸载

时间:2020-01-02 19:56:05

标签: javascript reactjs react-hooks gatsby

我正在使用盖茨比。

我有这个useEffect()钩子,用于在文档中添加和删除事件监听器,因此我可以跟踪外部点击以关闭相应菜单。但是,当我导航到另一条路线并打开菜单时,应用程序中断,并且出现此错误:

header.js:121 Uncaught TypeError: Cannot read property 'contains' of null
    at handleDropdown (header.js:121)
    at HTMLDocument.<anonymous> (header.js:134)

我有3个事件侦听器,因此每次在任何地方单击都会出现此错误三次。我添加了一个控制台日志,并意识到在路由更改期间,ref的值会暂时变为空。我觉得问题是因为header.js卸载然后重新安装。但是我在useEffect钩子中添加了一个返回值,该钩子应该删除事件侦听器,并在加载新路由时将其添加回去。我在上面做错了什么吗?

下面是我的功能和useEffect钩子。

// Function to close menus on outside clicks
  const handleDropdown = (menuRef, buttonRef, handler, e) => {
    console.log(!!menuRef.current, !!buttonRef.current)

    if (
      menuRef.current.contains(e.target) ||
      buttonRef.current.contains(e.target)
    ) {
      return
    }

    handler(false)
  }

  useEffect(() => {
    document.addEventListener("mousedown", e =>
      handleDropdown(mobileMenuRef, menuButtonRef, setShowMobileMenu, e)
    )

    document.addEventListener("mousedown", e =>
      handleDropdown(coursesMenuRef, coursesButtonRef, setShowCoursesMenu, e)
    )

    document.addEventListener("mousedown", e =>
      handleDropdown(
        schedulesMenuRef,
        schedulesButtonRef,
        setShowSchedulesMenu,
        e
      )
    )

    document.addEventListener("scroll", handleFixedNavbar)

    return () => {
      document.removeEventListener("mousedown", e =>
        handleDropdown(mobileMenuRef, menuButtonRef, setShowMobileMenu, e)
      )

      document.removeEventListener("mousedown", e =>
        handleDropdown(coursesMenuRef, coursesButtonRef, setShowCoursesMenu, e)
      )

      document.removeEventListener("mousedown", e =>
        handleDropdown(
          schedulesMenuRef,
          schedulesButtonRef,
          setShowSchedulesMenu,
          e
        )
      )

      document.removeEventListener("scroll", handleFixedNavbar)
    }
  }, [])

1 个答案:

答案 0 :(得分:1)

在JavaScript中,功能通过引用进行比较。这意味着() => {} !== () => {}。当您尝试在useEffect清理回调中删除事件侦听器时,您将传递新定义的函数,但是由于上述原因,它们将与您定义的现有处理程序不匹配。

如果您重构代码以在useEffect挂钩中包含事件处理程序,则可以将相同的hook-local变量传递给两个(add|remove)EventListener调用:

const useOnClickOutside = (refs, callback) => {
  useEffect(() => {
    const eventHandler = e => {
      if (refs.some(ref => ref.current.contains(e.target))) {
        return
      } else {
        callback(false)
      }
    }

    document.addEventListener("mousedown", eventHandler)

    return () => {
      document.removeEventListener("mousedown", eventHandler)
    }
  }, [refs, callback])
}

const YourComponent = () => {
  const someRef = useRef()
  const someOtherRef = useRef()
  useOnClickOutside([someRef, someOtherRef], () => {
    console.log("Click outside happened!")
  })

  return <div>Some Content</div>
}