5

I have a react component that relies on the google maps js api. This is imported in the head of the App.js page with a script tag. When I try to load the page containing this component, it fails, claiming that the script is not loaded. If, however, I first open a different page (which causes the script to load, but does not include this component), and then navigate to the page containing the component, it works.

This suggests to me that the issue is that the script has not yet loaded when the component loads. I thought, though, that the page loading should block while it gets the script. I'm not really sure how to fix this.

App.js:

class App extends Component {
  render() {
    return (
      <Router>
        <div>
          <Helmet>
            <script
              type="text/javascript"
              src="https://maps.googleapis.com/maps/api/js?key=AIzaSyDSn_vNbNZzrcFxl8bV3MH1j0kuoLVsOCQ&libraries=places"
            />
          </Helmet>

          <div className="pageContent">
                <Route exact path="/foo" component={FooScreen} />
                <Route exact path="/bar" component={BarScreen} /
          </div>
        </div>
      </Router>
    );
  }
}
export default App;

In the above, FooScreen contains the component, and BarScreen doesn't. If I open FooScreen directly, it fails, if I open BarScreen, and then navigate to FooScreen, it works.

Alex
  • 2,270
  • 3
  • 33
  • 65
  • Have you tried putting the google maps script before your `bundle.js` script or whatever you call your own compiled script? – Tholle Nov 04 '18 at 18:22
  • Please, provide https://stackoverflow.com/help/mcve that could replicate the problem, so it could be fixed. – Estus Flask Nov 04 '18 at 18:26
  • @estus Sure, I thought it might be a quick answer, so I didn't include it, but I've added the code now – Alex Nov 04 '18 at 18:28
  • I see. Since you need to track script loading, I don't think that Helmet is a good way to do this. I guess you need https://www.npmjs.com/package/react-async-script instead. – Estus Flask Nov 04 '18 at 18:33
  • @estus fair enough. I've tried to incorporate react-async-script, but it is not doing anything. The AsyncScriptLoader is present in the react dom, but the behaviour is as before – Alex Nov 04 '18 at 18:55
  • Obviously, this depends on how you used it. There's a bunch of React and non-React script loading libs, some of them may be more suitable but library cherry-picking is out of the scope of SO questions. In this case Helmet is able to provide more concise code. – Estus Flask Nov 05 '18 at 22:31

1 Answers1

3

Usually event listeners should be attached to script element instance, e.g. promise-based script loader.

This case is specific to Google Maps or other scripts that have asynchronous initialization routine and accept a callback, so it's redundant to listen to load event:

  state = { mapLoaded: false };

  componentDidMount() {
    window.onGoogleMap = () => this.setState({ mapLoaded: true });
  }

  componentWillUnmount() {
    delete window.onGoogleMap;
  }

  render() {
    return <>
      <Helmet>
        <script
          type="text/javascript"
          src="https://maps.googleapis.com/maps/api/js?key=...&libraries=places&callback=onGoogleMap"
        />
      </Helmet>

      {!this.state.mapLoaded ? (
        'Loading...'
      ) : (
        <Router>...</Router>
      )}
    </>;
  }

Since global variable is in use, this requires no more than one instance of App to exist at the same time, including tests.

Estus Flask
  • 206,104
  • 70
  • 425
  • 565