π React hook for using Portals
Need to make dropdowns, lightboxes/modals/dialogs, global message notifications, or tooltips in React? React Portals provide a first-class way to render children into a DOM node that exists outside the DOM hierarchy of the parent component (react docs).
This hook is also isomorphic, meaning it works with SSR (server side rendering).
- SSR (server side rendering) support
- TypeScript support
- 1 dependency (use-ssr)
- Built in state
- SSR Example - Next.js - codesandbox container (sometimes buggy, if so try this example)
- Modal Example (useModal) - create-react-app
- Dropdown Example (useDropdown) - Next.js
- Tooltip Example (useTooltip) - Next.js
yarn add react-useportal      or     npm i -S react-useportalimport usePortal from 'react-useportal'
const App = () => {
  const { Portal } = usePortal()
  return (
    <Portal>
      This text is portaled at the end of document.body!
    </Portal>
  )
}
const App = () => {
  const { Portal } = usePortal({
    bindTo: document && document.getElementById('san-francisco')
  })
  return (
    <Portal>
      This text is portaled into San Francisco!
    </Portal>
  )
}import usePortal from 'react-useportal'
const App = () => {
  var { openPortal, closePortal, isOpen, Portal } = usePortal()
  // want to use array destructuring? You can do that too
  var [openPortal, closePortal, isOpen, Portal] = usePortal()
  return (
    <>
      <button onClick={openPortal}>
        Open Portal
      </button>
      {isOpen && (
        <Portal>
          <p>
            This Portal handles its own state.{' '}
            <button onClick={closePortal}>Close me!</button>, hit ESC or
            click outside of me.
          </p>
        </Portal>
      )}
    </>
  )
}import usePortal from 'react-useportal'
const App = () => {
  const { openPortal, closePortal, isOpen, Portal } = usePortal()
  return (
    <>
      <button onClick={openPortal}>
        Open Portal
      </button>
      <Portal>
        <p className={isOpen ? 'animateIn' : 'animateOut'}>
          This Portal handles its own state.{' '}
          <button onClick={closePortal}>Close me!</button>, hit ESC or
          click outside of me.
        </p>
      </Portal>
    </>
  )
}By using onOpen, onClose or any other event handler, you can modify the Portal and return it. See useDropdown for a working example. If opening the portal from a click event it's important that you pass the event object to openPortal and togglePortal otherwise you will need to attach a ref to the clicked element (if you want to be able to open the portal without passing an event you will need to set programmaticallyOpen to true).
const useModal = () => {
  const { isOpen, openPortal, togglePortal, closePortal, Portal } = usePortal({
    onOpen({ portal }) {
      portal.current.style.cssText = `
        /* add your css here for the Portal */
        position: fixed;
        left: 50%;
        top: 50%;
        transform: translate(-50%,-50%);
        z-index: 1000;
      `
    }
  })
  return {
    Modal: Portal,
    openModal: openPortal,
    toggleModal: togglePortal,
    closeModal: closePortal,
    isOpen
  }
}
const App = () => {
  const { openModal, closeModal, isOpen, Modal } = useModal()
  
  return <>
    <button onClick={e => openModal(e)}>Open Modal<button>
    {isOpen && (
      <Modal>
        This will dynamically center to the middle of the screen regardless of the size of what you put in here
      </Modal>
    )}
  </>
}Make sure you are passing the html synthetic event to the openPortal and togglePortal . i.e. onClick={e => openPortal(e)}
If for some reason, you don't want to pass around the event to openPortal or togglePortal and you're not using programmaticallyOpen, you can use a ref like this.
import usePortal from 'react-useportal'
const App = () => {
  var { ref, openPortal, closePortal, isOpen, Portal } = usePortal()
  return (
    <>
      {/* see below how I don't have to pass the event if I use the ref */}
      <button ref={ref} onClick={() => openPortal()}>
        Open Portal
      </button>
      {isOpen && (
        <Portal>
          <p>
            This Portal handles its own state.{' '}
            <button onClick={closePortal}>Close me!</button>, hit ESC or
            click outside of me.
          </p>
        </Portal>
      )}
    </>
  )
}| Option | Description | 
|---|---|
| closeOnOutsideClick | This will close the portal when not clicking within the portal. Default is true | 
| closeOnEsc | This will allow you to hit ESC and it will close the modal. Default is true | 
| bindTo | This is the DOM node you want to attach the portal to. By default it attaches to document.body | 
| isOpen | This will be the default for the portal. Default is false | 
| onOpen | This is used to call something when the portal is opened and to modify the css of the portal directly | 
| onClose | This is used to call something when the portal is closed and to modify the css of the portal directly | 
| onPortalClick | This is fired whenever clicking on the Portal | 
| html event handlers (i.e. onClick) | These can be used instead of onOpento modify the css of the portal directly.onMouseEnterandonMouseLeaveexample | 
| programmaticallyOpen | This option allows you to open or toggle the portal without passing in an event. Default is false | 
const {
  openPortal,
  closePortal,
  togglePortal,
  isOpen,
  Portal,
  // if you don't pass an event to openPortal, closePortal, or togglePortal and you're not using programmaticallyOpen, you will need
  // to put this on the element you want to interact with/click
  ref,
  // if for some reason you want to interact directly with the portal, you can with this ref
  portalRef,
} = usePortal({
  closeOnOutsideClick: true,
  closeOnEsc: true,
  bindTo, // attach the portal to this node in the DOM
  isOpen: false,
  // `event` has all the fields that a normal `event` would have such as `event.target.value`, etc.
  // with the additional `portal` and `targetEl` added to it as seen in the examples below
  onOpen: (event) => {
    // can access: event.portal, event.targetEl, event.event, event.target, etc.
  },
  // `onClose` will not have an `event` unless you pass an `event` to `closePortal`
  onClose({ portal, targetEl, event }) {},
  // `targetEl` is the element that you either are attaching a `ref` to
  // or that you are putting `openPortal` or `togglePortal` or `closePortal` on
  onPortalClick({ portal, targetEl, event }) {},
  // in addition, any event handler such as onClick, onMouseOver, etc will be handled the same
  onClick({ portal, targetEl, event }) {}
})-  React Native support. 1 2 3 4 5 Probably going to have to add a Provider...
- add correct typescript return types
- add support for popup windows resource 1 resource 2. Maybe something like
  const { openPortal, closePortal, isOpen, Portal } = usePortal({
    popup: ['', '', 'width=600,height=400,left=200,top=200']
  })
  // window.open('', '', 'width=600,height=400,left=200,top=200')- tests (priority)
-  maybe have a <Provider order={['Portal', 'openPortal']} />then you can change the order of the array destructuring syntax
- fix code so maintainability is A
- set up code climate test coverage
-  optimize badges see awesome badge list
- add code climate test coverage badge
 
