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.
createPortal
seem to "slow" propagation? Especially if the event is being registered after rendering is complete? The app works as expected withoutcreatePortal
. – Amylaceous