Search code examples
javascriptreactjsyoutube-apies6-promise

ReactJS using multiple iframe vids only 1 works, others are Promise pending


Problem fixed and codes updated

Put youtubeReady Promise into another file(youtubeReady.js) and import from it :

const YTReady = new Promise( (resolve) => {
  window.onYouTubeIframeAPIReady = () => resolve(window.YT);
});

export default YTReady;

Code updated marked files


Post updated:

I have tried the potential solution according to HMR's answer.

But it didn't work...

(codes below have been updated as well)

I replace the original codes:

this.player = new Promise (...)
this.player.then(...)

with a new approach

const youtubeReady = new Promise(...)
youtubeReady.then(...)

The result is the video did not even shows up in firstCmp nor secondCmp class obj(constructor).

I used console.log(this) in componentDidMount() of these 2 components and no video was loaded.

Did I do it right? or I missed something?


Original Post content

Well, last few days I solved my previous problems to get Youtube-api working via mouseOver/mouseLeave events.

Now I got a new problem which I am struggling with for 2 days.

I got 3 components.

First to set script tag to get api (app.js) :

import React, {Component} from "react";
import ReactDom from "react-dom";
import './app.scss';

import FirstCmp from './firstCmp.js'
import secondCmp from './secondCmp.js'

class App extends Component {

  componentDidMount() {
    let tag = document.createElement('script');
        tag.src = 'https://www.youtube.com/iframe_api';
    let firstScriptTag = document.getElementsByTagName('script')[0];
        firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
  }

  render() {
    return (
        <div id="app">
          <FirstCmp />
          <SecondCmp />
        </div>
    )
  }
}

ReactDom.render(<App/>, document.getElementById('root'))

Then to create a video (firstCmp.js):

import React, { Component } from 'react';
import style from './firstCmp.scss';
import YTReady from './resource/youtubeReady';

export default class FirstCmp extends Component {

  constructor(props) {

    super(props);
    // just for example, not real code
    this.state = {
      iframeId: "myIframe",
      src: "src"
    }

  }

   componentDidMount() {

     let youtubeReady = YTReady;

     youtubeReady.then( (YT) => {
       this.player = new YT.Player(this.state.iframeId, {
          events: {
            'onReady': () => this.onPlayerReady(),
            'onStateChange': () => this.onPlayerStateChange()
          }
       })
     })
   }


   mouseOver() {

      this.player.playVideo();

   }

   mouseLeave() {

      this.player.pauseVideo();

   }

   render() {

     return (
      <section className={style.firstCmpContent}>

        <iframe
          id={this.state.iframeId}
          src={`${this.state.src}`}
          frameBorder="0"
          style={style.firstCmp}
          onMouseOver={ () => this.mouseOver() }
          onMouseLeave={ () => this.mouseLeave() } >
        </iframe>

      </section>
    );
   }
}

So far so good. The first video works fine. Until I create the second video...(secondCmp.js)

import React, { Component } from "react";
import style from './secondCmp.scss';
import YTReady from './resource/youtubeReady';


export default class SecondCmp extends Component {
    constructor(props) {

        super(props);
        // just for example, not real code
        this.state = {
          iframeId: "myIframe",
          src: "src"
        }

    }

    componentDidMount() {

      let youtubeReady = YTReady;

      youtubeReady.then( (YT) => {
        this.player = new YT.Player(iframeId, {
           events: {
              'onReady': () => this.onPlayerReady(),
              'onStateChange': () => this.onPlayerStateChange()
           }
        })
      })
    }


   mouseOver() {

      this.player.playVideo();

   }

   mouseLeave() {

      this.player.pauseVideo();

   }

   render() {

     return (
      <section className={style.secondCmpContent}>

        <iframe
          id={this.state.iframeId}
          src={`${this.state.src}`}
          frameBorder="0"
          style={style.secondCmp}
          onMouseOver={ () => this.mouseOver() }
          onMouseLeave={ () => this.mouseLeave() } >
        </iframe>

      </section>
    );
   }
}

Now, only the second video works, the first one gets promise pending!

If I create a third video, then the second and first get promise pending and only the new one(third) is working and so on.

How do I fix this problem?


Solution

  • Create a global promise because onYouTubeIframeAPIReady is called when scripts are loaded and that should happen only once, it will be ready every time after that you can find a similar question here. Currently you are overwriting the onYouTubeIframeAPIReady with a different function every time.

    So you can have one promise or service (not one for each component):

      const youtubeReady = new Promise( (resolve) => {
        window.onYouTubeIframeAPIReady = () => resolve(window.YT);
      });
    

    Then in your component:

      youtubeReady.then( (YT) => {
        this.player = new YT.Player(iframeId, {//not sure where iframeId comes from
           events: {
              'onReady': () => this.onPlayerReady(),
              'onStateChange': () => this.onPlayerStateChange()
           }
        })
      })