Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

HTML script is loading AFTER react components

My index.html

<!DOCTYPE html>
<html lang="en">
    <head>

        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
        <meta name="google-signin-client_id" content= "my_client_id.apps.googleusercontent.com">

        <meta name="google-signin-scope" content="profile email">
        <script src="https://apis.google.com/js/client:platform.js?onload=start" async defer></script>
        <script>
            function start() {
                console.log('script running')
                gapi.load('auth2', function() {
                    auth2 = gapi.auth2.init({
                        client_id: 'my_client_id.apps.googleusercontent.com',
                        scope: 'profile email'
                    });
                });
            }
        </script>
    <title>React App</title>
  </head>
  <body>
    <div id="root"></div>


  </body>
</html>

In the start() function I print to the console to see when it's running.

When I load the page, every so often start() will load after the react components.

Login.js

    componentDidMount() {
        console.log(gapi.auth2.getAuthInstance())
    }

In the debugger you can see that the script is loading after the component:

enter image description here

If I refresh the page a few times, it works fine. But sometimes it works, sometimes it doesn't.

Why?

like image 547
Morgan Allen Avatar asked Nov 21 '17 19:11

Morgan Allen


People also ask

How do I load a script after page load in React?

We start by creating an empty <script></script> tag in the memory as script and then assign the necessary attributes to its src and the id to identify the script later. Finally, we append the script to our <body></body> tag to actually load this.

Can we use React component in HTML?

Simply adding React components into HTML code is not possible, because <MyComponent></MyComponent> is not HTML at all, it is JSX.

Can we use script tag in React?

Since React doesn't support the <script> tag in Component , here are a few ways of appending JS files to specific components.

Does React load all components at once?

By default, React bundles the entire codebase and deploys it all at the same time. Normally, that's fine because React single-page applications (SPAs) are tiny. But if you're working with a more complex app like a content management system with a customer portal, loading the entire program right away isn't ideal.


2 Answers

I think the best way to load scripts in react is with a container component. It's a pretty simple component and it allows to you write the logic for importing the script in a component rather than your index.html. You are also going to want to make sure you don't include the script more than once by calling loadScript after a check in componentDidMount.

This is adapted from: https://www.fullstackreact.com/articles/how-to-write-a-google-maps-react-component/

Something like this. . .

  componentDidMount() {
    if (!window.google) {
      this.loadMapScript();
    }
    else if (!window.google.maps) {
      this.loadMapScript();
    }
    else {
      this.setState({ apiLoaded: true })
    }
  }

  loadMapScript() {
    // Load the google maps api script when the component is mounted.

    loadScript('https://maps.googleapis.com/maps/api/js?key=YOUR_KEY')
      .then((script) => {
        // Grab the script object in case it is ever needed.
        this.mapScript = script;
        this.setState({ apiLoaded: true });
      })
      .catch((err: Error) => {
        console.error(err.message);
      });
  }

  render() {
    return (
      <div className={this.props.className}>
        {this.state.apiLoaded ? (
          <Map
            zoom={10}
            position={{ lat: 43.0795, lng: -75.7507 }}
          />
        ) : (
          <LoadingCircle />
        )}
      </div>
    );
  }

and then in a separate file:

const loadScript = (url) => new Promise((resolve, reject) => {
  let ready = false;
  if (!document) {
    reject(new Error('Document was not defined'));
  }
  const tag = document.getElementsByTagName('script')[0];
  const script = document.createElement('script');

  script.type = 'text/javascript';
  script.src = url;
  script.async = true;
  script.onreadystatechange = () => {
    if (!ready && (!this.readyState || this.readyState === 'complete')) {
      ready = true;
      resolve(script);
    }
  };
  script.onload = script.onreadystatechange;

  script.onerror = (msg) => {
    console.log(msg);
    reject(new Error('Error loading script.'));
  };

  script.onabort = (msg) => {
    console.log(msg);
    reject(new Error('Script loading aboirted.'));
  };

  if (tag.parentNode != null) {
    tag.parentNode.insertBefore(script, tag);
  }
});


export default loadScript;

I know it's a lot but when I was first doing this it was so relieving when I found out there was a (fairly) simple way of including any script in any react component.

Edit: Some of this I just copy pasted but if you aren't using create-react-app you will probably have to replace some of the ES6 syntax.

like image 105
Dakota Avatar answered Oct 10 '22 15:10

Dakota


My suggestions:

Change your google API script tag to be this, where you remove async and defer

<script src="https://apis.google.com/js/client:platform.js"></script>

Get rid of your start function, which will now run the console.log fine and dandy, but the second bit of code will cause the same issue as it will also be running asynchronously.

Modify your react code, so that the componentWillMount calls the contents of that function instead:

componentWillMount() {
  gapi.load('auth2', () => {
    auth2 = gapi.auth2.init({
      client_id: 'urhaxorid.apps.googleusercontent.com',
      scope: 'profile email',
      onLoad: () => {
        this.setState({ mapLoaded: true });
      }
    });
  });
}

componentDidMount() {
  if (this.state.mapLoaded) {
    console.log(gapi.auth2.getAuthInstance());
  }
}

Please bear in mind that I don't know what the onLoad is for google apis, and I am not 100% sure how to best do the setState stuff, but it may be a starting block.

like image 33
Matt Fletcher Avatar answered Oct 10 '22 14:10

Matt Fletcher