Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Opening other portals doesn't count as outside click #175

Open
ranneyd opened this issue Nov 7, 2017 · 8 comments
Open

Opening other portals doesn't count as outside click #175

ranneyd opened this issue Nov 7, 2017 · 8 comments

Comments

@ranneyd
Copy link

ranneyd commented Nov 7, 2017

My problem

I have a situation where I have a list of items with little arrows for dropdowns that are going in portals. I'm using PortalWithState with closeOnOutsideClick for the dropdowns, and buttons with onClick={openPortal} as the arrows. When I have one of them open and I outside click it usually works fine, but when I click on one of the other arrows, it opens that portal without closing the open one.

My theory:

In every scenario I've encountered, custom click-events are resolved after react ones, so the click on the arrow should resolve first. That click triggers openPortal, which does e.nativeEvent.stopImmediatePropagation(). I think that's stopping the propagation that would trigger the outside click listener.

My (gross) solution:

This is a workaround that currently works for my current use case.

const fakeEvent = {
  nativeEvent: {
    stopImmediatePropagation: () => {},
  },
};
setTimeout(() => openPortal(fakeEvent), 0);

The setTimeout forces the portal opening to the bottom of the queue (so the outside click resolves first). However, React recycles the event handler, so I can't pass it directly to openPortal. I have to pass this mock one that doesn't actually do anything so I don't get errors for things being undefined. This currently does what I want (closes the current dropdown and opens the other one).

@ranneyd
Copy link
Author

ranneyd commented Nov 7, 2017

NOTE: I've discovered that this makes clicking on the arrow for the original dropdown reopen the portal immediately after it closes. I have to work around that by only binding my toggle event when !isOpen is true.

@tajo
Copy link
Owner

tajo commented Nov 14, 2017

Any idea how to fix this and keep the current functionality (not reopening portal when you click on the button) ?

@sontx
Copy link

sontx commented Nov 20, 2017

I think we don't need to pass an event to openPortal anymore, use a "opening" flag

function openPortal() {
      var _this1 = this;
      if (_this1.state.active) {
        return;
      }
      // turn the "opening" flag to true to prevent handleOutsideMouseClick method close the portal
      _this1.setState({ active: true, opening: true }, _this1.props.onOpen);
      // turn the "opening" flag to false it means the portal already opened, so the handleOutsideMouseClick method can handle again
      setTimeout(function () {
	 _this1.setState({ active: true, opening: false })
      }, 0);
}
function handleOutsideMouseClick(e) {
      if (!this.state.active || this.state.opening) {// if we are opening the portal, so we must ignore click outside event
        return;
      }
      var root = findDOMNode(this.portalNode);
      if (!root || root.contains(e.target) || e.button && e.button !== 0) {
        return;
      }
      this.closePortal();
}

It just a trick but it works for me :D, sorry for my bad english and my "built code" because I just modified the "built" code insteads of source code.

@webholics
Copy link

stopPropagation should always be considered harmful, because other components on a page cannot react to a click anymore.

@mnmistake
Copy link

So, I'm facing this same issue any idea?

@Frondor
Copy link

Frondor commented Feb 18, 2020

Same issue as of v4.2.1

@alekslario
Copy link

bump

@alekslario
Copy link

alekslario commented Jun 30, 2020

Couldn't find a solution to this issue so I used a global redux/context store + custom hook fallback.

import React, { useEffect, useState } from "react";
import { useStore } from "./contextStore";
import shortid from "shortid";
export const useCloseModals = (closePortal) => {
  const [store, dispatch] = useStore();
  const [id] = useState(shortid.generate());

  useEffect(() => {
    const handle = setTimeout(() => {
      if (store.activeModal !== id) closePortal();
    }, 0);
    return () => clearTimeout(handle);
  }, [store.activeModal]);

  useEffect(() => {
    dispatch({ type: "SET_ACTIVE_MODAL", id });
  }, []);
};

Works well

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants