How to implement Google Maps search box in a React application

zeee picture zeee · Apr 10, 2020 · Viewed 8.9k times · Source

I'm new to React and Google Maps. I'm using google-map-react to integrate Google Maps to my React application. I was able to successfully load the map and add markers.

But I'm getting an error when trying to add the SearchBox. I followed the documentation here SeachBox Documentation and also the issue thread GitHub issue. But still I'm getting the error. What is wrong in this code?

enter image description here

Here is my code

App.js

import React, { Component } from 'react';
import GoogleMapReact from 'google-map-react';
import './App.css';
import Driver from './Driver';
import Passenger from './Passenger';
import SearchBox from './SearchBox';


class App extends Component {

  constructor(props) {
    super(props);
    this.state = {
      apiReady: false,
      map: null,
      googlemaps: null
    };
  }

  static defaultProps = {
    center: {
      lat: 6.92,
      lng: 79.86
    },
    zoom: 15,
  };

  handleApiLoaded = (map, maps) => {
    // use map and maps objects
    if (map && maps) {
      this.setState({
        apiReady: true,
        map: map,
        googlemaps: maps
      });
    }
  };

  render({ apiReady, googlemaps, map } = this.state) {
    return (
      // Important! Always set the container height explicitly
      <div style={{ height: '100vh', width: '100%' }}>
        <GoogleMapReact
          bootstrapURLKeys={{ key: 'AIzaSyCk7pbkmNhknGumy2vgDykdgVj6lSreTt0', libraries: ['places'] }}
          defaultCenter={this.props.center}
          defaultZoom={this.props.zoom}
          yesIWantToUseGoogleMapApiInternals
          onGoogleApiLoaded={({ map, maps }) => this.handleApiLoaded(map, maps)}
        >
          <Driver
            lat={6.8972152}
            lng={79.8541014}
          />
          <Passenger
            lat={6.9272012}
            lng={79.8681316}
          />

          {apiReady && (<SearchBox
            //  placeholder={"123 anywhere st."}
            //  onPlacesChanged={this.handleSearch} 
            map={map}
            googlemaps={googlemaps} />)}
        </GoogleMapReact>
      </div>
      )
  }
}

export default App

SearchBox.js

import React from 'react';
import ReactDOM from 'react-dom'
import PropTypes from 'prop-types'

export default class SearchBox extends React.Component {

  static propTypes = {
    placeholder: PropTypes.string,
    onPlacesChanged: PropTypes.func
  }
  render() {
    return <input ref="input" placeholder={this.props.placeholder} type="text"/>;
  }
  onPlacesChanged = () => {
    if (this.props.onPlacesChanged) {
      this.props.onPlacesChanged(this.searchBox.getPlaces());
    }
  }
  componentDidMount() {
    var input = ReactDOM.findDOMNode(this.refs.input);
    // eslint-disable-next-line no-undef
    this.searchBox = new googlemaps.places.SearchBox(input);
    this.searchBox.addListener('places_changed', this.onPlacesChanged);
  }
  componentWillUnmount() {
    this.searchBox.removeListener('places_changed', this.onPlacesChanged);
  }

}

// eslint-disable-next-line no-unused-expressions
// eslint-disable-next-line no-lone-blocks
{/* <script defer type="text/javascript" src="https://maps.google.com/maps/api/js?key=AIzaSyCk7pbkmNhknGumy2vgDykdgVj6lSreTt0&libraries=places"></script> */}

I have pasted the full code because I'm not sure where I went wrong.

Answer

Theroux picture Theroux · Aug 6, 2020

google-map-react prop bootstrapURLKeys gives the possibility to avoid inserting script directly to HTML

<GoogleMapReact
    bootstrapURLKeys={{
        key: 'XXXXXXXXXXXXXXXXXXX',
        libraries: 'places'
    }}
......
/>

Also here is a code snippet for Searchbox using hooks (don't forget to memoize onPlacesChanged callback using useCallback in case of parent functional component):

const SearchBox = ({ maps, onPlacesChanged, placeholder }) => {
    const input = useRef(null);
    const searchBox = useRef(null);

    const handleOnPlacesChanged = useCallback(() => {
        if (onPlacesChanged) {
            onPlacesChanged(searchBox.current.getPlaces());
        }
    }, [onPlacesChanged, searchBox]);

    useEffect(() => {
        if (!searchBox.current && maps) {
            searchBox.current = new maps.places.SearchBox(input.current);
            searchBox.current.addListener('places_changed', handleOnPlacesChanged);
        }

        return () => {
            if (maps) {
                searchBox.current = null;
                maps.event.clearInstanceListeners(searchBox);
            }
        };
    }, [maps, handleOnPlacesChanged]);

    return <input ref={input} placeholder={placeholder} type="text" />;
};