sugarshin/react-instagram-embed

Cannot find Embeds of undefined

Opened this issue ยท 17 comments

Hey nice library, worked straight away

I just ran into one issue when I upgraded to react 16.

Your checkAPI() function is returning too early (window.instgrm isn't found yet), and then calling handleFetchSuccess which errors since window.instgrm.Embeds can't be found.

I added my own code around yours which which sets a state variable based on <script async onload={this.onLoad} />

loadInstagram() {  // called from componentWillMount()
    if (!window.instgrm) {
      const s = document.createElement('script')
      s.async = s.defer = true
      s.src = `https://platform.instagram.com/en_US/embeds.js`
      s.id = 'react-instagram-embed-script'
      s.onload = this.onLoad
      const body: HTMLElement | null = document.body
      if (body) {
        body.appendChild(s)
      }
    }
  }

Hey @CooperSystems do you have a full example of your work around, i've run into the same issue with React 16... ty!

Same @CooperSystems could you post how you got it working?
Or @PaulieScanlon did you get it working?

Having probably the same issue on react 16 that the component is not being found and getting back
TypeError: Cannot read property 'Embeds' of undefined

Yep here's what I ended up doing, extracting the load function out of the library into my own wrapper. It's a workaround though and a pull request would be far better

// @flow

import React from 'react'
import InstagramEmbed from 'react-instagram-embed'

type Props = {
  url?: string
}
type State = {
  isLoaded: boolean
}

export default class InstagramLoader extends React.Component<Props, State> {
  static defaultProps = {}
  onLoad: Function
  loadInstagram: Function
  constructor() {
    super()

    this.onLoad = this.onLoad.bind(this)
    this.loadInstagram = this.loadInstagram.bind(this)
  }
  state = {
    isLoaded: false
  }
  componentWillMount() {
    this.loadInstagram()
  }
  loadInstagram() {
    if (!window.instgrm) {
      const s = document.createElement('script')
      s.async = s.defer = true
      s.src = `https://platform.instagram.com/en_US/embeds.js`
      s.id = 'react-instagram-embed-script'
      s.onload = this.onLoad
      const body: HTMLElement | null = document.body
      if (body) {
        body.appendChild(s)
      }
    }
  }
  onLoad() {
    console.log('Loaded instagram')
    this.setState({
      isLoaded: true
    })
  }
  render() {
    const { url = '' } = this.props
    const { isLoaded } = this.state

    const css = {
      outer: {},
      blank: {
        padding: 12
      }
    }

    return (
      <div style={css.outer}>
        {url.length > 0 && isLoaded ? (
          <InstagramEmbed url={url} hideCaption />
        ) : (
          <div style={css.blank}>Instagram URL not provided</div>
        )}
      </div>
    )
  }
}

Having the same problem. Are there any plans on including this somehow into this lib?
Thanks to @CooperSystems for investigating and finding the workaround!

I just adapted @CooperSystems solution for our setup (no flow) and added the other parameters as props. Maybe it will help somebody...

InstagramLoader.jsx

'use strict'

import React, { Component } from "react";
import InstagramEmbed from 'react-instagram-embed'


export default class InstagramLoader extends Component {
  
  constructor(props) {
    super(props)

    this.state = {
        isLoaded: false
    }

    this._onLoad = this._onLoad.bind(this)
    this._loadInstagram = this._loadInstagram.bind(this)
  }

  _loadInstagram() {
    if (!window.instgrm) {
      const s = document.createElement('script')
      s.async = s.defer = true
      s.src = `https://platform.instagram.com/en_US/embeds.js`
      s.id = 'react-instagram-embed-script'
      s.onload = this._onLoad
      const body = document.body || null
      if (body) {
        body.appendChild(s)
      }
    }
  }

  _onLoad() {
    console.log('Loaded instagram')
    this.setState({
      isLoaded: true
    })
  }

  componentDidMount() {
    this._loadInstagram()
  }

  render() {
    const { 
      url = '', 
      hideCaption = false,
      maxWidth = '',
      containerTagName = 'div',
      protocol = '',
      onLoading = () => {},
      onSuccess= () => {},
      onAfterRender=() => {},
      onFailure=() => {},

    } = this.props
    const { isLoaded } = this.state

    const css = {
      outer: {},
      blank: {
        padding: 12
      }
    }

    return (
      <div style={css.outer}>
        {url.length > 0 && isLoaded ? (
          <InstagramEmbed key={url}
            url={url}
            hideCaption={hideCaption}
            maxWidth={maxWidth}
            containerTagName={containerTagName}
            protocol={protocol}
            onLoading={onLoading}
            onSuccess={onSuccess}
            onAfterRender={onAfterRender}
            onFailure={onFailure}
          />
        ) : (
          <div style={css.blank}>Instagram URL not provided</div>
        )}
      </div>
    )
  }
}

Then just import it and use it like you would InstagramEmbed.

eseQ commented

Same issue in production

@FelixButzbach 's code works great for one embeded item, but the issue returns when trying to render more than one item. Below is my code - on first render it works as expected. But on page reload the Embed error returns. Any ideas?

      { ["Bg7Jh6YnECP","Bg7CB5fl3rg","Bg7BOBRlOm7"].map((item) => {
      return ( 
         <InstagramEmbed
           key={`https://instagr.am/p/${item}`}
           url={`https://instagr.am/p/${item}`}
           maxWidth={320}
           hideCaption={false}
           containerTagName='div'
           protocol=''
           onLoading={() => {console.log('loading')}}
           onSuccess={() => {console.log('loaded')}}
           onAfterRender={() => {console.log('rendered')}}
           onFailure={() => {console.log('failed')}}
        />
        ) 
      })
    } 

@supertopoz I just tested my code with more than one (two) embedded instagram posts and didn't have any problems. Maybe it is related to the map (the multiple _onload functions are called at the same time, or something like this)? And just to be sure, you created a module named InstagramLoader, imported it and used this instead of the original InstagramEmbed, right? (I am asking because in your example code it looks like you are using the original InstagramEmbeded Component)

@FelixButzbach This is the weirdest thing. Instead of importing you code as you suggest (I totally missed that part). Instead I added your code into the component and continued to use the original library.

Finally I stripped it down to just runnning @CooperSystems loadInstagram() function with a tiny delay of 100ths of a second along with the original library, and works a treat, no "Embed" warning for 1 or many posts.

This perhaps indicates that the orignal bug is comming from the instragram JS needing to be added last on the page, after it's dependancies are ready.

import React from 'react'
import { Link} from 'react-router-dom'
import styled from 'styled-components'
import { history } from 'react-router'
import InstagramEmbed from 'react-instagram-embed'

const Content = styled.div`
  display: grid;
  grid-gap: 1px;
  grid-template-columns: repeat(auto-fill, 320px);
  border: 1px solid;
  max-height: 750px;
  overflow-y: auto;
  align-content: space-between;
  justify-content: space-around;
`;

class  Gallery extends React.Component {
  constructor(props) {
    super(props)
    this.loadInstagram = this.loadInstagram.bind(this)
  }

  loadInstagram() {
    if (!window.instgrm) {
      const s = document.createElement('script')
      s.async = s.defer = true
      s.src = `https:\//platform.instagram.com/en_US/embeds.js`
      s.id = 'react-instagram-embed-script'
      s.onload = this._onLoad
      const body = document.body || null
      if (body) {
        body.appendChild(s)
      }
    }
  }

  componentDidMount() {
    setTimeout(() => {
      this.loadInstagram()
    },100)
  }

  render() {
    const tags = ["BhDJVNpFVIc","BhDI_6CF9Ks","BhDIoeeF7dJ" , (52+ posts in total)];
    return (
      <Content>
      { tags.map((item, index) => {
      return (  <InstagramEmbed
          key={ index }
          url={`https://instagr.am/p/${item}`}
          maxWidth={320}
          hideCaption={false}
          containerTagName='div'
          protocol=''
          onLoading={() => {console.log()}}
          onSuccess={() => {console.log()}}
          onAfterRender={() => {console.log()}}
          onFailure={() => {console.log()}}
        />
        ) 
     })
    }    
    </Content>
        ) 
  }
}

export default Gallery

@supertopoz weird indeed. Glad that you figured a way. I always try to avoid relaying on timeouts, what if your code runs on a slower device or with slow internet connection? I just implemented a google maps map and had a problem with the google maps sdk not beeing loaded on time. So I created a selfcalling function that runs every 500 ms and only executes the maps code once it finds the global google object. I then stop the function from calling itself again and execute the corresponding maps code. You could set a flag in the state and only show the Instagram Components once the flag is true. There might be a better (reactier) way of doing this, though...

Hi, everyone I finally solved this issue without any modification to the code. I added the below tag to the head of my app. I removed the "defer" loading, and the "Embed" issue is gone, without the need to make any modifications. Let me know if this works for you.

<script src="//www.instagram.com/embed.js"></script>   

@supertopoz Your solution works for me :)

Hey ! I've got this issue appartently only with SSR on. (You can try on nextjs or razzle)
After the first load, no problem at all.

Well, looking at the code, it seems to be not compatible with SSR. Right ?

lfaz commented

has anyone yet solved this issue on reactjs?

I tried to add in the header <script src="//www.instagram.com/embed.js"></script> and window.instgrm.Embeds.process(); in componentDidMount still not working

We are using the plugin successfully in production with SSR. Here is a link that shows the plugin in action: https://www.antena1.com.br/noticias/mariah-carey-publica-video-cantando-com
I am using the code that I posted earlier (#21 (comment)).