Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I make React Portal work with React Hook?

Tags:

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>
like image 442
Rafael 'BSIDES' Pereira Avatar asked Dec 03 '18 14:12

Rafael 'BSIDES' Pereira


People also ask

Where do I use React portals?

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.

When using a Portal what is the second argument React?

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.

Is React and React Hooks the same?

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.

What version of React do you use with Hooks?

Hooks were added to React in version 16.8.


1 Answers

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) }  
like image 142
Samuel Avatar answered Oct 17 '22 16:10

Samuel