React - `createPortal` with `document.body` causes click event to fire immediately

2k Views Asked by At

My app has a button called Open Modal. When this button is clicked, a modal is appended to the document.body using createPortal. Link to demo

Once the modal has been mounted (i.e. isOpen === true), I would like to print some text to the console each time the user clicks somewhere on the page.

To do this, I'm using a useEffect hook to bind a click event to the document object if isOpen === true:

useEffect(() => {
    const eventHandler = () => console.log("You clicked me");

    if (isOpen) {
      document.addEventListener("click", eventHandler);
    }

  }, [isOpen]);

When the user clicks 'Open Modal' for the first time, isOpen changes from false to true, this causes the component to re-render. Once the re-render is complete, the click event in the above effect is registered.

Problem

Clicking 'Open Modal' for the first time causes the document's click event to fire (you can see the event handler's output in the console). This doesn't make sense to me - how is it possible for the click event on document to already be registered by the time the first 'Open Modal' click has propagated up to it?

Expected behaviour

The first click on 'Open Modal' should only result in the document's click event being registered, the event itself should not fire at this point.

If I omit createPortal (see line 38 of the demo), the app works as expected, i.e. the event is registered on the first click on 'Open Modal' and the event handler is called on all subsequent clicks on the page.

The app also works as expected if I mount the modal to a sibling of the app root instead of document.body (see line 39 of the demo). So the problem described above only occurs when createPortal is used with document.body as the container, but I don't understand why.

1

There are 1 best solutions below

6
On

I'm not sure exactly, But I think, the problem occurring because of Event Propagation, By the time propagation completes, document click event is been registered, that might be reason it's calling the eventHandler. Correct me if am I'm wrong.

If you stop the event propagation, it's working fine.

const openModal = useCallback(
    function (e) {
      if (!isOpen) { // stopping the propagation
        e.stopPropagation();
      }
      setOpen(true);
    },
    [setOpen, isOpen]
  );

Demo link here