I have this specific need to listen to a custom event in the browser and from there, I have a button that will open a popup window. I'm currently using React Portal to open this other window (PopupWindow), but when I use hooks inside it doesn't work - but works if I use classes. By working I mean, when the window opens, both shows the div below it but the one with hooks erases it when the data from the event refreshes. To test, leave the window open for at least 5 seconds.
I have an example in a CodeSandbox, but I'm also post here in case the website is down or something:
https://codesandbox.io/s/k20poxz2j7
The code below won't run because I don't know how to make react hooks work via react cdn but you can test it with the link above by now
const { useState, useEffect } = React; function getRandom(min, max) { const first = Math.ceil(min) const last = Math.floor(max) return Math.floor(Math.random() * (last - first + 1)) + first } function replaceWithRandom(someData) { let newData = {} for (let d in someData) { newData[d] = getRandom(someData[d], someData[d] + 500) } return newData } const PopupWindowWithHooks = props => { const containerEl = document.createElement('div') let externalWindow = null useEffect( () => { externalWindow = window.open( '', '', `width=600,height=400,left=200,top=200` ) externalWindow.document.body.appendChild(containerEl) externalWindow.addEventListener('beforeunload', () => { props.closePopupWindowWithHooks() }) console.log('Created Popup Window') return function cleanup() { console.log('Cleaned up Popup Window') externalWindow.close() externalWindow = null } }, // Only re-renders this component if the variable changes [] ) return ReactDOM.createPortal(props.children, containerEl) } class PopupWindow extends React.Component { containerEl = document.createElement('div') externalWindow = null componentDidMount() { this.externalWindow = window.open( '', '', `width=600,height=400,left=200,top=200` ) this.externalWindow.document.body.appendChild(this.containerEl) this.externalWindow.addEventListener('beforeunload', () => { this.props.closePopupWindow() }) console.log('Created Popup Window') } componentWillUnmount() { console.log('Cleaned up Popup Window') this.externalWindow.close() } render() { return ReactDOM.createPortal( this.props.children, this.containerEl ) } } function App() { let data = { something: 600, other: 200 } let [dataState, setDataState] = useState(data) useEffect(() => { let interval = setInterval(() => { setDataState(replaceWithRandom(dataState)) const event = new CustomEvent('onOverlayDataUpdate', { detail: dataState }) document.dispatchEvent(event) }, 5000) return function clear() { clearInterval(interval) } }, []) useEffect( function getData() { document.addEventListener('onOverlayDataUpdate', e => { setDataState(e.detail) }) return function cleanup() { document.removeEventListener( 'onOverlayDataUpdate', document ) } }, [dataState] ) console.log(dataState) // State handling const [isPopupWindowOpen, setIsPopupWindowOpen] = useState(false) const [ isPopupWindowWithHooksOpen, setIsPopupWindowWithHooksOpen ] = useState(false) const togglePopupWindow = () => setIsPopupWindowOpen(!isPopupWindowOpen) const togglePopupWindowWithHooks = () => setIsPopupWindowWithHooksOpen(!isPopupWindowWithHooksOpen) const closePopupWindow = () => setIsPopupWindowOpen(false) const closePopupWindowWithHooks = () => setIsPopupWindowWithHooksOpen(false) // Side Effect useEffect(() => window.addEventListener('beforeunload', () => { closePopupWindow() closePopupWindowWithHooks() }) ) return ( <div> <button type="buton" onClick={togglePopupWindow}> Toggle Window </button> <button type="buton" onClick={togglePopupWindowWithHooks}> Toggle Window With Hooks </button> {isPopupWindowOpen && ( <PopupWindow closePopupWindow={closePopupWindow}> <div>What is going on here?</div> <div>I should be here always!</div> </PopupWindow> )} {isPopupWindowWithHooksOpen && ( <PopupWindowWithHooks closePopupWindowWithHooks={closePopupWindowWithHooks} > <div>What is going on here?</div> <div>I should be here always!</div> </PopupWindowWithHooks> )} </div> ) } const rootElement = document.getElementById('root') ReactDOM.render(<App />, rootElement)
<script crossorigin src="https://unpkg.com/[email protected]/umd/react.development.js"></script> <script crossorigin src="https://unpkg.com/[email protected]/umd/react-dom.development.js"></script> <div id="root"></div>
This React portal component exists outside the DOM hierarchy of the parent component. When the child component visually separates out from its parent component, the portal can be used as a modal dialog box, tooltip, or loader to create the child component.
Portals provide a first-class way to render children into a DOM node that exists outside the DOM hierarchy of the parent component. The first argument ( child ) is any renderable React child, such as an element, string, or fragment. The second argument ( container ) is a DOM element.
Hooks are a new addition in React 16.8. They let you use state and other React features without writing a class. Read the Motivation to learn why we're introducing Hooks to React.
Hooks were added to React in version 16.8.
Thought id chime in with a solution that has worked very well for me which creates a portal element dynamically, with optional className and element type via props and removes said element when the component unmounts:
export const Portal = ({ children, className = 'root-portal', element = 'div', }) => { const [container] = React.useState(() => { const el = document.createElement(element) el.classList.add(className) return el }) React.useEffect(() => { document.body.appendChild(container) return () => { document.body.removeChild(container) } }, []) return ReactDOM.createPortal(children, container) }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With