Search code examples
javascriptreactjsevent-handlingevent-bubbling

React portal event bubbling in the wrong direction


In my app I created a React portal which consists of a div covering the entire document and contains a div (with some other content):

public render(): React.ReactNode {
    const { children, container = document.body } = this.props;
    const { open, options } = this.state;

    const className = this.getEffectiveClassNames([
        "portal",
        this.classFromProperty(options?.ignoreMouseEvents, "ignoreMouse"),
    ]);

    return (
        open && createPortal(
            <div
                ref={this.backgroundRef}
                className={className}
                style={{ "--background-opacity": options?.backgroundOpacity }}
                onMouseDown={this.handlePortalClick}
                {...this.unhandledProperties}
            >
                {children}
            </div>, container,
        )
    );
}

I want to hide the portal when the user clicks on the background div (not the content):

private handlePortalClick = (e: React.MouseEvent): void => {
    const { open, options } = this.state;

    if (open && options.closeOnPortalClick && e.target === this.backgroundRef.current) {
        this.close();
    }
};

I registered 2 mouse down handlers: one on the background div and one on the content. When I click on the content I first get the onMouseDown event for the background div and after that the handler for the content is triggered. This is odd, given that the content is a child of the background and I'm not using the capturing event handlers. In this situation I would expect to first get the event in the content handler and after that in the container.

Now I have to explicitly check if the event target is the background div, which works, but shouldn't be necessary. Can someone explain why the event invocation order is reversed in this case?


Solution

  • The reason for the misbehavior is a simple mistake. The portal click handler is not hooked up to onClick but to onMouseDown (while the content event handler is set to onClick). Since a click is generated from a mouse down, followed by a mouse up, the event chain is first run for the portal only (the content has no handler for that).