usePortal
🌀 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).
Features
- SSR (server side rendering) support
- TypeScript support
- 1 dependency (use-ssr)
- Built in state
Examples
Installation
yarn add react-useportal or npm i -S react-useportal
Usage
Stateless
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>
)
}
With State
import usePortal from 'react-useportal'
const App = () => {
var { openPortal, closePortal, isOpen, Portal } = usePortal()
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>
)}
</>
)
}
Need Animations?
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>
</>
)
}
Customizing the Portal directly
By using onOpen
, onClose
or any other event handler, you can modify the Portal
and return it. See useDropdown for a working example. 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.
const useModal = () => {
const { isOpen, 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,
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)}
Usage with a ref
If for some reason, you don't want to pass around the event
to openPortal
or togglePortal
, 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>
)}
</>
)
}
Options
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 |
Option Usage
const {
openPortal,
closePortal,
togglePortal,
isOpen,
Portal,
ref,
} = usePortal({
closeOnOutsideClick: true,
closeOnEsc: true,
bindTo,
isOpen: false,
onOpen: (event) => {
},
onClose({ portal, targetEl, event }) {},
onPortalClick({ portal, targetEl, event }) {},
onClick({ portal, targetEl, event }) {}
})
Todos
const { openPortal, closePortal, isOpen, Portal } = usePortal({
popup: ['', '', 'width=600,height=400,left=200,top=200']
})