π 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-useportal
import 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 onOpen to modify the css of the portal directly. onMouseEnter and onMouseLeave example |
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