简体   繁体   English

反应,点击外部事件在点击打开后立即发生,防止模式显示

[英]React, click outside event happens right after click to open, preventing the modal from showing

On button click I want the Modal to appear.单击按钮时,我希望Modal显示。 The Modal component adds a eventListener so it closes when you click outside the modal. Modal组件添加了一个eventListener ,因此当您在模态框外单击时它会关闭。 In React 18 the click event triggers because the button click that happend before Modal was rendered?在 React 18 中,单击事件触发是因为在呈现 Modal之前发生的按钮单击? If I change to react 17 this does not happen.如果我更改为 react 17 这不会发生。

Find a CodeSandbox here . 在此处查找 CodeSandbox。 Notice, when you click the button the show state sets to true.请注意,当您单击按钮时, show状态设置为 true。 Then the Modal component renders and calls the close function directly.然后Modal组件直接渲染并调用close函数。

App.js:应用程序.js:

import { useState } from "react";
import Modal from "./Modal";
import "./styles.css";

export default function App() {
  const [show, setShow] = useState(false);

  const close = () => {
    setShow(false);
  };

  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <button
        onClick={(e) => {
          setShow(true);
        }}
      >
        Click
      </button>
      {show && <Modal close={close} />}
    </div>
  );
}

Modal.js模态.js

import "./styles.css";
import { useRef, useEffect } from "react";

export default function Modal({ close }) {
  const ref = useRef(null);

  useEffect(() => {
    const handleOutsideClick = (e) => {
      if (!ref?.current?.contains(e.target)) {
        console.log("This one gets called because of the button click", e);
        close();
      }
    };

    document.addEventListener("click", handleOutsideClick, false);
    return () => {
      document.removeEventListener("click", handleOutsideClick, false);
    };
  }, [close]);

  return (
    <div ref={ref} className="Modal">
      <h1>I'm a Modal!</h1>
    </div>
  );
}

You can call event.stopPropagation to prevent multiple click handlers capturing the same click event.您可以调用event.stopPropagation以防止多个单击处理程序捕获相同的单击事件。

    onClick={(e) => {
      e.stopPropagation();
      setShow(true);
    }}

I don't know why this would differ between React 17 and 18. React uses its own "synthetic events", and there might have been a change in how event propagation/bubbling happens between the two versions.我不知道为什么这在 React 17 和 18 之间会有所不同。React 使用它自己的“合成事件”,并且可能在两个版本之间发生的事件传播/冒泡方式发生了变化。

It might be connected to what's called "automatic batching" in React 18. https://github.com/reactwg/react-18/discussions/21它可能与 React 18 中所谓的“自动批处理”有关。 https://github.com/reactwg/react-18/discussions/21

In your example, the Modal component uses native event handling with document.addEventlistener() .在您的示例中, Modal 组件使用带有document.addEventlistener()的本机事件处理。 It seems that React 18 handles the click inside the app, which triggers a state change and a rerender, mounts the Modal component, runs the useEffect() hook, and creates the new event listener before the click event is propagated to the window node.似乎 React 18 处理了应用程序内部的点击,这会触发状态更改和重新渲染,挂载Modal组件,运行useEffect()钩子,并在点击事件传播到窗口节点之前创建新的事件监听器。 In React 17, the event presumably finishes propagating before the re-render happens.在 React 17 中,事件可能在重新渲染发生之前完成传播。

You could change your if statement in handleOutsideClick as below.您可以在handleOutsideClick中更改if语句,如下所示。 Yours is not working because the button for opening the Modal , since it's not inside the Modal , passes this if(!ref?.current?.contains(e.target)) you have.你的不起作用,因为打开Modalbutton ,因为它不在Modal内,所以传递了这个if(!ref?.current?.contains(e.target))你有。

Here is a working fork of your CodeSandbox. 这是您的 CodeSandbox 的一个工作分支 I'm passing the button's ref to Modal .我将按钮的ref传递给Modal And here is what I changed:这是我改变的:

const handleOutsideClick = (e) => {
  if (!ref?.current?.contains(e.target) && !openButtonRef?.current?.contains(e.target)) {
    console.log("This one gets called because of the button click", e);
    close();
  }
};

A different solution to the problem involves new startTransition API: https://codesandbox.io/s/gracious-benz-0ey6ol?file=/src/App.js:340-388该问题的另一种解决方案涉及新的startTransition API: https ://codesandbox.io/s/gracious-benz-0ey6ol?file=/src/App.js:340-388

onClick={(e) => {
  startTransition(() => {
    setShow(true);
  })
}}

This new API ( doc ) definitely helps, but I'm not sure this is a legit solution, and docs are not quite clear on that.这个新的 API ( doc ) 肯定有帮助,但我不确定这是一个合法的解决方案,文档对此也不太清楚。 Would be nice if someone from React can comment -- is this an abuse?如果来自 React 的人可以发表评论,那就太好了——这是滥用吗?

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

相关问题 React,在打开事件后立即单击外部事件触发,防止显示模式 - React, click outside event fire right after the open event, preventing the modal from being displayed 在外部单击时反应关闭模式 - React close modal on click outside 第二次单击后模态未显示在我的反应 boostrap 表中 - After the second click Modal is not showing in my react boostrap table 处理右键单击事件后未显示上下文菜单 - not showing the context menu after handling right click event 如何防止在 React Modal 之外点击? - How to prevent click outside React Modal? 是否添加在嵌入式点击事件之后发生的点击事件? - Add a click event that happens after inline click event? 单击时保持 wistia 覆盖层打开 - 外部框上的 e.stopPropogation 防止其关闭 - stopPropgation 误解 - keep wistia overlay open on click - e.stopPropogation on outside box preventing it from closing - stopPropgation misunderstanding 从另一个组件打开模态点击反应js - Open modal from another component click in react js 在模态视频外单击后在后台播放 - After click outside the modal video play in background 如何通过点击事件从外部调用 function - How to call a function from outside in react through the click event
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM