Using Google Place Autocomplete API in React

Here's a solution using ES6 + React Hooks:

First, create a useGoogleMapsApi hook to load the external script:

import { useEffect, useState, useCallback } from 'react'
import loadScript from 'load-script'
import each from 'lodash/each'

var googleMapsApi
var loading = false
var callbacks = []

const useGoogleMapsApi = () => {
  const [, setApi] = useState()

  const callback = useCallback(() => {
    setApi(window.google.maps)
  }, [])

  useEffect(() => {
    if (loading) {
      callbacks.push(callback)
    } else {
      if (!googleMapsApi) {
        loading = true
        loadScript(
          `https://maps.googleapis.com/maps/api/js?key=${process.env.REACT_APP_GOOGLE_MAPS_API_KEY}&libraries=places`,
          { async: true },
          () => {
            loading = false
            googleMapsApi = window.google.maps
            setApi(window.google.maps)
            each(callbacks, init => init())
            callbacks = []
          })
      }
    }
  }, [])

  return googleMapsApi
}

export default useGoogleMapsApi

Then, here's your input component:

import React, { useRef, useEffect, forwardRef } from 'react'
import useGoogleMapsApi from './useGoogleMapsApi'

const LocationInput = forwardRef((props, ref) => {
  const inputRef = useRef()
  const autocompleteRef = useRef()
  const googleMapsApi = useGoogleMapsApi()

  useEffect(() => {
    if (googleMapsApi) {
      autocompleteRef.current = new googleMapsApi.places.Autocomplete(inputRef.current, { types: ['(cities)'] })
      autocompleteRef.current.addListener('place_changed', () => {
        const place = autocompleteRef.current.getPlace()
        // Do something with the resolved place here (ie store in redux state)
      })
    }
  }, [googleMapsApi])

  const handleSubmit = (e) => {
    e.preventDefault()
    return false
  }

  return (
    <form autoComplete='off' onSubmit={handleSubmit}>
      <label htmlFor='location'>Google Maps Location Lookup</label>
      <input
        name='location'
        aria-label='Search locations'
        ref={inputRef}
        placeholder='placeholder'
        autoComplete='off'
      />
    </form>
  )
}

export default LocationInput

Viola!


Was making a custom address autocomplete for a sign up form and ran into some issues,

// index.html imports the google script via script tag  ie: <script src="https://maps.googleapis.com/maps/api/js?key=MYKEY&libraries=places"></script>


import {useState, useRef, useEffect } from 'React'

function AutoCompleteInput(){

const [predictions, setPredictions] = useState([]);
const [input, setInput] = useState('');
const [selectedPlaceDetail, addSelectedPlaceDetail] = useState({})
const predictionsRef = useRef();


useEffect(
()=>{
      try {
        autocompleteService.current.getPlacePredictions({ input }, predictions => {
          setPredictions(predictions);
        });
      } catch (err) {
       // do something
      }
    }
}, [input])

const handleAutoCompletePlaceSelected = placeId=>{
 if (window.google) {
      const PlacesService = new window.google.maps.places.PlacesService(predictionsRef.current);
      try {
        PlacesService.getDetails(
          {
            placeId,
            fields: ['address_components'],
          },
         place => addSelectedPlaceDetail(place)
        );
      } catch (e) {
        console.error(e);
      }
    }
}

return (
  <>
   <input onChange={(e)=>setInput(e.currentTarget.value)}
    <div ref={predictionsRef}
     { predictions.map(prediction => <div onClick={ ()=>handleAutoCompletePlaceSelected(suggestion.place_id)}> prediction.description </div> )
   }
   </div>
  <>
 )
}

So basically, you setup the autocomplete call, and get back the predictions results in your local state.

from there, map and show the results with a click handler that will do the follow up request to the places services with access to the getDetails method for the full address object or whatever fields you want.

you then save that response to your local state and off you go.


Google Maps API loading via static import:

import "https://maps.googleapis.com/maps/api/js?key=MYKEY&libraries=places&callback=initMap";

is not supported, you need to consider a different options for that purpose:

  • reference Google Maps API JS library via /public/index.html file: <script src="https://maps.googleapis.com/maps/api/js?key=MYKEY&libraries=places"></script>
  • or dynamically load JS resource, for example using this library

Now regarding SearchBar component, the below example demonstrates how to implement a simple version of Place Autocomplete (without a dependency to Google Map instance) based on this official example

import React from "react";
/* global google */


class SearchBar extends React.Component {
  constructor(props) {
    super(props);
    this.autocompleteInput = React.createRef();
    this.autocomplete = null;
    this.handlePlaceChanged = this.handlePlaceChanged.bind(this);
  }

  componentDidMount() {
    this.autocomplete = new google.maps.places.Autocomplete(this.autocompleteInput.current,
        {"types": ["geocode"]});

    this.autocomplete.addListener('place_changed', this.handlePlaceChanged);
  }

  handlePlaceChanged(){
    const place = this.autocomplete.getPlace();
    this.props.onPlaceLoaded(place);
  }



  render() {
    return (
        <input ref={this.autocompleteInput}  id="autocomplete" placeholder="Enter your address"
         type="text"></input>
    );
  }
}