How can I create a separate dynamic window in my React app?

538 Views Asked by At

My React app incorporates a video chat function (via Twilio). The user goes to a dashboard and then clicks a button to start the call. This prompts the VideoCall component to be instantiated and shown. On instantiation, it connects to a backend Twilio service to get an access token, and then connects to Twilio to create the call, set up events handlers etc.

Currently I'm showing the video windows in a div within the dashboard, but I would like them to appear in a pop-out window instead. I've tried using react-new-window and I've tried React Portals, but I didn't know enough about what I was doing to make it work.

Currently I have the following Dashboard component:

function Dashboard(props) {

  const { displayInfo} = props
  const [ callInitiated, setCallInitiated ] = useState(false)

  const initCall = () => {
    setCallInitiated(true)
  }

  return (
    <div id="dashboard">
      {callInitiated ? <VideoCall displayInfo={displayInfo} /> : null }
      <div> ...rest of dashboard, including button which calls 'initCall' on being clicked... </div>
    </div>
  )

}

export default Dashboard

My VideoCall component is:

const Video = require('twilio-video');

// Get access token from backend service
async function getAccessToken(identity) {

    const url = `${process.env.REACT_APP_TWILIO_TOKEN_SERVICE}?identity=${identity}`;
    try {
        const response = await axios.get(`${url}`, AXIOS_HEADERS);
        return response.data.accessToken;
    } catch {
        return null;
    }

}

// VideoCall component
function VideoCall(props) {

    const { displayInfo} = props

    // Connect to Twilio server function to get access token
    getAccessToken('Tester')
    .then((token) => {
      Video.connect(token, {
        name: 'Test room'
      })
      .then(room => {
        participantConnected(room.localParticipant);
        room.participants.forEach(participantConnected);
        room.on('participantConnected', participantConnected);
        room.on('participantDisconnected', participantDisconnected);
        room.once('disconnected', error => room.participants.forEach(participantDisconnected))
      })
    });

    function participantConnected(participant) {
    
      const div = document.createElement('div');
      div.id = participant.sid;
    
      participant.on('trackSubscribed', track => trackSubscribed(div, track));
      participant.on('trackUnsubscribed', trackUnsubscribed);
    
      participant.tracks.forEach(publication => {
          trackSubscribed(div, publication.track);
      });

      if(participant.identity === 'Tester') {
        document.getElementById('myVideoWindow').appendChild(div)
      }
      
    }
    
    function participantDisconnected(participant) {
      document.getElementById(participant.sid).remove();
    }
    
    function trackSubscribed(div, track) {
      div.appendChild(track.attach());
    }
    
    function trackUnsubscribed(track) {
      track.detach().forEach(element => element.remove());
    }

    return (
          <div id="callWrapper" className="callOuterWrapper">
              <div className="titleBar">
                  <h1 className="pageHeader">Calling {displayInfo.receiverName}</h1>
              </div>
              <div className="videoRoom">
                    <div id="myVideoWindow" className="callWindow"></div>
                    <div className="callInfo"> ...this will contain info on call status... </div>
                    <div id="receiverWindow" className="callWindow"></div>
              </div>
          </div>
    )
}

export default VideoCall

So far, this works. The user clicks the button and the video windows appear at the top of the dashbaord, as expected.

Now I want to pull the VideoCall component out into a separate window (so that the user can still see the dashboard while on the call.

I tried the package react-new-window, which just involved wrapping the VideoCall in a NewWindow. I tried wrapping it within the Dashboard component:

<div id="dashboard">
  {callInitiated ? <NewWindow><VideoCall displayInfo={displayInfo} /></NewWindow> : null }
  <div> ...rest of dashboard, including button which calls 'initCall' on being clicked... </div>
</div>

and when that didn't work I tried wrapping within the VideoCall component:

<NewWindow>
   <div id="callWrapper" className="callOuterWrapper">...</div>
</NewWindow>

In both cases this displayed the new window with the empty callWrapper div; however, once it reached document.getElementById('myVideoWindow').appendChild(div) it was unable to find the div. The DOM being referenced appears to be the one from the Dashboard window rather than the new windows (also, any console.log commands get logged to the console of the original window, not the new one).

I then tried taking apart the NewWindow code and creating my own bespoke version, but I don't know enough about how it works to make it do what I needed.

So, is there a way to access the DOM of the new window from the component within it? Or is there a different approach I should be taking?

1

There are 1 best solutions below

3
On BEST ANSWER

Twilio developer evangelist here.

Directly accessing the DOM with native DOM methods, like document.getElementById, is a little frowned upon within React. React itself should be in charge of adding and removing things from the DOM.

I wrote a post on how to build a video chat with React that covers how to add your participants to the page without accessing the DOM directly.

I'd recommend a look through that and perhaps updating your app so that you don't have to use document.getElementById and then hopefully the <NewWindow> component should work as advertised.