Search code examples
javascriptgoogle-mapsreactjsgoogle-maps-api-3react-on-rails

getPlace function not working with Google Autocomplete in react


I'm trying to recreate this Google autocomplete example in react: https://developers.google.com/maps/documentation/javascript/examples/places-autocomplete-addressform

I am using this tutorial to generate the higher order component needed to properly load the Google API: https://www.fullstackreact.com/articles/how-to-write-a-google-maps-react-component/

The autocomplete functionality seems to be working just fine. However, when I select a suggestion and try to run fillInAddress() this is the error message that I get:

Uncaught TypeError: Cannot read property 'getPlace' of undefined

This is my code /components/GoogleApiComponent.jsx

import React from 'react';
import {GoogleApiWrapper} from 'google-maps-react';
import GoogleAutoComplete from '../components/GoogleAutoComplete';


export class Container extends React.Component {
  render() {
    if (!this.props.loaded) {
      return <div>Loading...</div>
    }
    return (
    <div>
      <GoogleAutoComplete 
      />
    </div>

    )
  }
}

export default GoogleApiWrapper({
  apiKey: 'somekey'
})(Container)

../components/GoogleAutoComplete.jsx

import React from 'react';

export default class GoogleAutoComplete extends React.Component {
  static propTypes = {
    }
  constructor(props) {
    super(props);
    }

    componentDidMount() {
      this.initAutocomplete();
    }

    initAutocomplete() {
      this.autocomplete = new google.maps.places.Autocomplete((this.refs.autoCompletePlaces), {types: ['geocode']});

      this.autocomplete.addListener('place_changed', this.fillInAddress);


    }

    geolocate() {
      if (navigator.geolocation) {
        navigator.geolocation.getCurrentPosition(function(position) {
          const geolocation = {
            lat: position.coords.latitude,
            lng: position.coords.longitude
          };
        });
      }
    }

    fillInAddress() {

      const componentForm = {
        street_number: 'short_name',
        route: 'long_name',
        locality: 'long_name',
        administrative_area_level_1: 'short_name',
        country: 'long_name',
        postal_code: 'short_name'
      };
    // Get the place details from the autocomplete object.
      const place = this.autocomplete.getPlace();
      for (let component in componentForm) {
        this.refs.component.value = '';
        this.refs.component.disabled = false;
      }

    // Get each component of the address from the place details
    // and fill the corresponding field on the form.
    for (let i = 0; i < place.address_components.length; i++) {
      const addressType = place.address_components[i].types[0];
      if (componentForm[addressType]) {
        const val = place.address_components[i][componentForm[addressType]];
        this.refs.addressType.value = val;
      }
    }
  }    

  render() {
    return (
      <div>
        <div>
          <input 
            placeholder="Enter your address"
            onFocus={this.geolocate}
            ref="autoCompletePlaces"
          />
        </div>
        <table ref="address">
          <tbody>
            <tr>
              <td>Street address</td>
              <td>
                <input 
                  ref="street_number"
                  disabled="true"/>
              </td>
              <td>
                <input 
                  ref="route"
                  disabled="true"/>
              </td>
            </tr>
            <tr>
              <td>City</td>
              <td>
                <input 
                  ref="locality"
                  disabled="true"/>
              </td>
            </tr>
            <tr>
              <td>State</td>
              <td>
                <input 
                  ref="administrative_area_level_1" 
                  disabled="true"/>
                </td>
              <td>Zip code</td>
              <td>
                <input
                  ref="postal_code"
                  disabled="true"/>
              </td>
            </tr>
            <tr>
              <td>Country</td>
              <td>
                <input
                  ref="country" 
                  disabled="true"/>
              </td>
            </tr>
          </tbody>
        </table>
      </div>      
    );
  }
}

Solution

  • I figured this out, with some help of the people over at discord, and this other stackoverflow question. As per that answer, the key point is "Inside the place_changed-callback the keyword this points to the object which has been triggered the event.". I made that change and it worked.
    This is the key line: this.place = this.autocomplete.getPlace();

    For completeness I also re-factored the form fill this.place.address_components.forEach((component, index) => {

     const addressType = this.place.address_components[index].types[0];
          if (componentForm[addressType]) {
            const val = this.place.address_components[index][componentForm[addressType]];
            this.refs[addressType].value = val;
          }
        })
    

    Code in its entirety is as follows:

    import React from 'react';
    
    export default class GoogleAutoComplete extends React.Component {
      static propTypes = {
        }
      constructor(props) {
        super(props);
          this.fillInAddress = this.fillInAddress.bind(this);
    
        }
    
        componentDidMount() {
          this.initAutocomplete();
        }
    
    
        initAutocomplete() {
          const google = window.google;
          this.autocomplete = new google.maps.places.Autocomplete((this.refs.autoCompletePlaces), {types: ['geocode']});
    
          this.autocomplete.addListener('place_changed', this.fillInAddress);
    
        }
    
        geolocate() {
          if (navigator.geolocation) {
            navigator.geolocation.getCurrentPosition(function(position) {
              const geolocation = {
                lat: position.coords.latitude,
                lng: position.coords.longitude
              };
            });
          }
        }
    
        fillInAddress() {
          const componentForm = {
            street_number: 'short_name',
            route: 'long_name',
            locality: 'long_name',
            administrative_area_level_1: 'short_name',
            country: 'long_name',
            postal_code: 'short_name'
          };
        // Get the place details from the autocomplete object.
          this.place = this.autocomplete.getPlace();
          /*this.setState({placeResult: this.place.address_components})*/
    
          for (let component in this.componentForm) {
            this.refs.component.value = '';
            this.refs.component.disabled = false;
          }
    
        // Get each component of the address from the place details
        // and fill the corresponding field on the form.
    
        this.place.address_components.forEach((component, index) => {
          const addressType = this.place.address_components[index].types[0];
          if (componentForm[addressType]) {
            const val = this.place.address_components[index][componentForm[addressType]];
            this.refs[addressType].value = val;
          }
        })   
      }    
    
      render() {
        return (
          <div>
            <div>
              <input 
                placeholder="Enter your address"
                onFocus={this.geolocate}
                ref="autoCompletePlaces"
                className="form-control"
                type="text"
              />
            </div>
            <table ref="address">
              <tbody>
                <tr>
                  <td>Street address</td>
                  <td>
                    <input 
                      ref="street_number"
                      disabled="true"
                      value=''
                      />
                  </td>
                  <td>
                    <input 
                      ref="route"
                      disabled="true"/>
                  </td>
                </tr>
                <tr>
                  <td>City</td>
                  <td>
                    <input 
                      ref="locality"
                      disabled="true"/>
                  </td>
                </tr>
                <tr>
                  <td>State</td>
                  <td>
                    <input 
                      ref="administrative_area_level_1" 
                      disabled="true"/>
                    </td>
                  <td>Zip code</td>
                  <td>
                    <input
                      ref="postal_code"
                      disabled="true"/>
                  </td>
                </tr>
                <tr>
                  <td>Country</td>
                  <td>
                    <input
                      ref="country" 
                      disabled="true"/>
                  </td>
                </tr>
              </tbody>
            </table>
          </div>      
        );
      }
    }