Gatsby: How to use Google Auto Complete API

Created on 10 May 2018  路  8Comments  路  Source: gatsbyjs/gatsby


Hi there,

I need to use Google Auto Complete API on my address search textbox.

I found this library:
https://github.com/kenny-hibino/react-places-autocomplete

Which asks for loading this script

into my project.

What I have tried?

  1. index.html
    If it's a pure Reactjs project, I would do it in /public/index.html, but as you may know that this file(index.html) gets deleted and re-generated every time when we run
    gatsby develop
    so it always loses the javascript reference after I re-run the command.
<!DOCTYPE html>
        <html>

        <head>
            <meta charSet="utf-8" />
            <meta http-equiv="x-ua-compatible" content="ie=edge" />
            <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
            <title data-react-helmet="true"></title>
            <script src="/socket.io/socket.io.js"></script>
            <script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?key={API_KEY}&libraries=places" async defer ></script>
        </head>

        <body>
            <div id="___gatsby"></div>
            <script src="/commons.js"></script>
        </body>

        </html>
  1. layout/index.js with React-Helmet

I included this script in-between ....here...

  <Helmet>
          <script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?key={API}&libraries=places&callback=initAutocomplete" async defer></script>
        </Helmet>

this sometime works, but when I refresh my page, it breaks and asks for loading the Google Map API lib.

Been stuck here for a day~!

Please help.

question or discussion

Most helpful comment

In case someone (like me) stumbles upon this looking for a way to implement a Google Maps component in Gatsby, here are two ways to do it.

Using html.js

Modifying src/html.js like so (as suggested above) is one option.

import React, { Component } from 'react'
import PropTypes from 'prop-types'

export default class HTML extends Component {
  render() {
    return (
      <html {...this.props.htmlAttributes}>
        <head>
          <meta charSet="utf-8" />
          <meta httpEquiv="x-ua-compatible" content="ie=edge" />
          <meta
            name="viewport"
            content="width=device-width, initial-scale=1, shrink-to-fit=no"
          />
          {this.props.headComponents}
        </head>
        <body {...this.props.bodyAttributes}>
          {this.props.preBodyComponents}
          <div
            key={`body`}
            id="___gatsby"
            dangerouslySetInnerHTML={{ __html: this.props.body }}
          />
          {this.props.postBodyComponents}
          // MODIFICATION
          // ===================
          <script
            src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY"
            async
            defer
          />
          // ===================
        </body>
      </html>
    )
  }
}

HTML.propTypes = {
  htmlAttributes: PropTypes.object,
  headComponents: PropTypes.array,
  bodyAttributes: PropTypes.object,
  preBodyComponents: PropTypes.array,
  body: PropTypes.string,
  postBodyComponents: PropTypes.array,
}

Then you can access the Google Maps API anywhere in your project from window.google.maps.[Map/Marker/etc.].

The React way

To me that felt a little anachronistic, though. If you want a reusable React component that you can import into any page or template as import Map from './Map', I suggest this instead.

// src/components/Map.js
import React, { Component } from 'react'

export default class Map extends Component {
  onLoad = () => {
    const map = new window.google.maps.Map(
      document.getElementById(this.props.id),
      this.props.options
    )
    this.props.onMount(map)
  }

  componentDidMount() {
    if (!window.google) {
      const script = document.createElement('script')
      script.type = 'text/javascript'
      script.src = `https://maps.google.com/maps/api/js?key=YOUR_API_KEY`
      const headScript = document.getElementsByTagName('script')[0]
      headScript.parentNode.insertBefore(script, headScript)
      script.addEventListener('load', () => {
        this.onLoad()
      })
    } else {
      this.onLoad()
    }
  }

  render() {
    return <div style={{ height: `50vh` }} id={this.props.id} />
  }
}

Use it like so:

// src/pages/contact.js
import React from 'react'

import Map from '../components/Map'

const center = { lat: 50, lng: 10 }
const mapProps = {
  options: {
    center,
    zoom: 8,
  },
  onMount: map => {
    new window.google.maps.Marker({
      position: center,
      map,
      title: 'Europe',
    })
  },
}

const Contact = () => {
  return (
        <h1>Contact</h1>
        <Map id="contactMap" {...mapProps} />
  )
}

export default Contact

All 8 comments

This could be documented better I think, but you can use the onRenderBody API to do this. Have a look at this example.

Oh and here's a link to the onRenderBody docs

You could also try customizing html.js.

Is using onRenderBody a better approach?

It's cleaner as the code can be put into a plugin. But either way works and for a one-off integration, editing html.js is often easier.

thanks guys, since I am not using Server Side Rendering, so the onRenderBody() doesn't fit for me.
I ended up using html.js and it works well :)

Thanks you all for your help, love Gatsbyjs ~~!!!

I'm trying to do something similar with the Maps JavaScript API but I'm confused as to where the Google code is supposed to go inside of html.js. I've tried before the closing </body> and also inside the <head> of html.js. In both cases, while I don't get any errors, I don't see any map either 馃槙

I figured it out... I needed to use dangerouslySetInnerHTML:

<script dangerouslySetInnerHTML={{
  __html: `
    let map;
    function initMap() {
      map = new google.maps.Map(document.getElementById('map'), {
        center: { lat: -34.397, lng: 150.644 },
        zoom: 8,
     });
   }
 `,
}} />

_However_, I'm still struggling to figure out how to have the map stay when navigating to a new page and back again. Map seems to load only on page reloads.

In case someone (like me) stumbles upon this looking for a way to implement a Google Maps component in Gatsby, here are two ways to do it.

Using html.js

Modifying src/html.js like so (as suggested above) is one option.

import React, { Component } from 'react'
import PropTypes from 'prop-types'

export default class HTML extends Component {
  render() {
    return (
      <html {...this.props.htmlAttributes}>
        <head>
          <meta charSet="utf-8" />
          <meta httpEquiv="x-ua-compatible" content="ie=edge" />
          <meta
            name="viewport"
            content="width=device-width, initial-scale=1, shrink-to-fit=no"
          />
          {this.props.headComponents}
        </head>
        <body {...this.props.bodyAttributes}>
          {this.props.preBodyComponents}
          <div
            key={`body`}
            id="___gatsby"
            dangerouslySetInnerHTML={{ __html: this.props.body }}
          />
          {this.props.postBodyComponents}
          // MODIFICATION
          // ===================
          <script
            src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY"
            async
            defer
          />
          // ===================
        </body>
      </html>
    )
  }
}

HTML.propTypes = {
  htmlAttributes: PropTypes.object,
  headComponents: PropTypes.array,
  bodyAttributes: PropTypes.object,
  preBodyComponents: PropTypes.array,
  body: PropTypes.string,
  postBodyComponents: PropTypes.array,
}

Then you can access the Google Maps API anywhere in your project from window.google.maps.[Map/Marker/etc.].

The React way

To me that felt a little anachronistic, though. If you want a reusable React component that you can import into any page or template as import Map from './Map', I suggest this instead.

// src/components/Map.js
import React, { Component } from 'react'

export default class Map extends Component {
  onLoad = () => {
    const map = new window.google.maps.Map(
      document.getElementById(this.props.id),
      this.props.options
    )
    this.props.onMount(map)
  }

  componentDidMount() {
    if (!window.google) {
      const script = document.createElement('script')
      script.type = 'text/javascript'
      script.src = `https://maps.google.com/maps/api/js?key=YOUR_API_KEY`
      const headScript = document.getElementsByTagName('script')[0]
      headScript.parentNode.insertBefore(script, headScript)
      script.addEventListener('load', () => {
        this.onLoad()
      })
    } else {
      this.onLoad()
    }
  }

  render() {
    return <div style={{ height: `50vh` }} id={this.props.id} />
  }
}

Use it like so:

// src/pages/contact.js
import React from 'react'

import Map from '../components/Map'

const center = { lat: 50, lng: 10 }
const mapProps = {
  options: {
    center,
    zoom: 8,
  },
  onMount: map => {
    new window.google.maps.Marker({
      position: center,
      map,
      title: 'Europe',
    })
  },
}

const Contact = () => {
  return (
        <h1>Contact</h1>
        <Map id="contactMap" {...mapProps} />
  )
}

export default Contact
Was this page helpful?
0 / 5 - 0 ratings