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

Parent portal closes when clicking inside of a nested portal #191

Open
zhouzi opened this issue Jan 9, 2018 · 3 comments
Open

Parent portal closes when clicking inside of a nested portal #191

zhouzi opened this issue Jan 9, 2018 · 3 comments

Comments

@zhouzi
Copy link

zhouzi commented Jan 9, 2018

This issue was originally discussed in #101, before the rewrite.

I created a bin reproducing it with the latest version of react-portal:
https://www.webpackbin.com/bins/-L2PNDWSpoEmiOUuzvFO

@kebot
Copy link

kebot commented Apr 9, 2018

In v3, portal component listen to mouseup and touchstart events, but in v4 it listen to click event, which not always works fine for me.

https://github.com/tajo/react-portal/blob/3.x/lib/portal.js#L27

@skimi
Copy link

skimi commented Aug 14, 2018

This can be fixed using event bubbling through the react-dom. The react doc has a chapter for this : https://reactjs.org/docs/portals.html#event-bubbling-through-portals

I personally created my own portal wrapper like this :

import uniqueId from 'lodash/uniqueId';
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';

class Portal extends Component {
  componentDidMount() {
    const { onClickOutside } = this.props;

    if (!onClickOutside) return;

    window.addEventListener('click', this.handleDocumentClick);
  }

  componentWillUnmount() {
    if (this.defaultNode) {
      document.body.removeChild(this.defaultNode);
    }

    this.defaultNode = null;
    window.removeEventListener('click', this.handleDocumentClick);
  }

  id = this.props.id || uniqueId()

  handleDocumentClick = (e) => {
    if (!e.parentModals || !e.parentModals.includes(this.id)) {
      this.props.onClickOutside(e);
    }
  }

  handleClickInsideModal = (e) => {
    const currentParentModals = e.nativeEvent.parentModals || [];

    e.nativeEvent.parentModals = [
      ...currentParentModals,
      this.id,
    ];
  }

  render() {
    const { children } = this.props;

    if (!this.defaultNode) {
      this.defaultNode = document.createElement('div');
      document.body.appendChild(this.defaultNode);
    }

    return ReactDOM.createPortal(
      <div onClick={this.handleClickInsideModal}>
        {children}
      </div>,
      this.defaultNode
    );
  }
}

Portal.propTypes = {
  children: PropTypes.node,
  onClickOutside: PropTypes.func,
  id: PropTypes.oneOfType([
    PropTypes.string,
    PropTypes.number,
  ])
};

export default Portal;

The onClick event that react creates passes through nested Portal.

@diegohaz
Copy link

You can also nest portals in the DOM:

import React from "react";
import ReactDOM from "react-dom";

const PortalContext = React.createContext(
  typeof document !== "undefined" ? document.body : null
);

export function Portal({ children }) {
  // if it's a nested portal, context is the parent portal
  // otherwise it's document.body
  const context = React.useContext(PortalContext);
  const [container] = React.useState(() => {
    if (typeof document !== "undefined") {
      const portal = document.createElement("div");
      portal.className = "portal";
      return portal;
    }
    // ssr
    return null;
  });

  React.useLayoutEffect(() => {
    if (container && context) {
      context.appendChild(container);
      return () => {
        context.removeChild(container);
      };
    }
    return undefined;
  }, [container, context]);

  if (container) {
    const portal = ReactDOM.createPortal(children, container);
    return (
      <PortalContext.Provider value={container}>
        {portal}
      </PortalContext.Provider>
    );
  }

  // ssr
  return null;
}

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

4 participants