How I use Google Map Javascript API without using any other lib in my NextJS project
Tags: webdev, react, nextjs
Published: 2020-03-28 05:30:49

Hello guys, this is my first post on dev.to. In this very first post, I want to share one of my experiences as a front-end engineer at Sayurbox (One of Indonesian Startup). 

As I said in the title of this post, it's a Google Map thing. When we are building our mobile web using NextJS, we want to create the features of this mobile web to be similar as our mobile app. One of those features is a Google Map place picker.

There is a famous lib that which allow us to use Google Map in our NextJS project called **google-map-react**, it's also fully isomorphic and can render on server, but my Senior said "why don't we just use the Google Map Javascript SDK", we only need to show a Google Map and a simple marker at the center of it. And I said, yes that's make sense and it's more challenging for me and I liked it.

Maybe I did a lot of wrong implementations but I just decided to not being shy or afraid that I will getting bullied, the reason why I share something is when I did something wrong, other people can show me the right way. So, please, if you guys have any feedback to improve my code, just leave it in the comment section below :).

## And then, some code...
After fiddling around, I finally found the simplest way to do it. I'm still figuring out to improve it's performance. The first thing I did is to load the Google Map Javascript SDK. There is a way to load it on client side using **scriptjs** lib as the **google-map-react** lib did, but I have tried it and it produces some bugs and i have no time to fix it, that's why I decided to just put a script loader tag on the head tag because the feature needs to be launched ASAP. But I will fix it later. This the code :
```javascript
...
import Head from 'next/head';
...

const AddressPage = () => {
  return (
    <App>
      <Head>
         <script async defer src={constants.GOOGLE_MAP_API_URL} />
      </Head>
      ...
    </App>
  )
};

...
```

After I loaded the Google Map Javascript API, I can use it in my React Google Map View Component. This is the code :
```javascript
/* eslint-disable no-undef */
import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { useSelector } from 'react-redux';
import icLocation from '~/public/assets/icons/ic_location.png';
import icMyLocation from '~/public/assets/icons/ic_my_location.png';
import { getCurrentLocation } from '~/lib/domUtil';
import styles from './styles.json';

const GoogleMapView = ({ width, height, lat, lng, zoom,
  zoomControl, scaleControl, fullscreenControl, disableDefaultUI, gestureHandling,
  mapRef, onDragStart, onDragEnd, showMyLocationButton }) => {    
    const mapDivRef = React.createRef();
    const isTest = useSelector(state => state.view.isTest);
    const [isDragging, setIsDragging] = useState(false);
    const [localMapRef, setlocalMapRef] = useState(null);
    const [latestLocation, setLatestLocation] = useState(null);

    const options = {
      center: {lat, lng},
      zoom,
      disableDefaultUI,
      zoomControl,
      scaleControl,
      fullscreenControl,
      clickableIcons: false,
      clickableLabels: false,
      styles,
    }

    /** 
     * This is hacky becase this google map using external script
     * so when we detect this is a test, the google map will not be rendered.
    */
    if(isTest) {
      return null;
    }

    useEffect(() => {
      if(localMapRef) {
        const newOptions = Object.assign({}, options);
        newOptions.gestureHandling = gestureHandling ? 'greedy' : 'none';
        localMapRef.setOptions(newOptions);
      }
    }, [localMapRef, gestureHandling]);

    useEffect(() => {
      if(localMapRef && lat && lng) {
        localMapRef.setCenter({lat, lng});
      }
    }, [localMapRef, lat, lng])

    useEffect(() => {
      let dragStartListener;
      let dragEndListener;  
      let map = null;    
      if(map === null && mapDivRef.current !== null) {
        onDragEnd(lat, lng);
        
        map = new google.maps.Map(mapDivRef.current, options);

        getCurrentLocation().then((position) => {
          const { latitude, longitude } = position.coords;          
          const location = {lat: latitude, lng: longitude};
          setLatestLocation(location);

          if(!lat || !lng) {
            onDragEnd(latitude, longitude);
            map.setCenter(location);
          }
        }).catch(() => {})

        dragStartListener = google.maps.event.addListener(map, 'dragstart', () => {
          setIsDragging(true);
          onDragStart();
        });

        dragEndListener = google.maps.event.addListener(map, 'dragend', () => {
          setIsDragging(false);
          const center = map.getCenter();
          onDragEnd(center.lat(), center.lng());
        });
      
        mapRef(map);
        setlocalMapRef(map);
      }

      return () => {        
        google.maps.event.removeListener(dragStartListener);
        google.maps.event.removeListener(dragEndListener);
      }
    }, []);

    return (
      <div
        style={{ width, height }}
        className="GoogleMapView__container"
      >
        <div
          style={{ width, height }}
          ref={mapDivRef} id="map"></div>
        <img
          id="ic-location"
          className={isDragging ? 'ic-location-up' : 'ic-location-down'}
          src={icLocation}
          alt="sayurbox-ic-location"
        />
        <i
          className={`ic-circle ${isDragging ? 'ic-circle-up' : 'ic-circle-down'}`}
        ></i>
        {(showMyLocationButton && latestLocation) &&
          <div
            className="GoogleMapView__container__icMyLocation"
            role="button"
            onClick={() => {
              if(latestLocation && localMapRef) {
                localMapRef.setCenter(latestLocation);
                onDragEnd(latestLocation.lat, latestLocation.lng)
              }
            }}
          >
            <img
              src={icMyLocation}
              alt="sayurbox-ic-my-location"            
            />
          </div>
        }        
      </div>
    )    
};

GoogleMapView.propTypes = {
  width: PropTypes.string,
  height: PropTypes.string,
  lat: PropTypes.number,
  lng: PropTypes.number,
  zoom: PropTypes.number,
  mapRef: PropTypes.func,
  disableDefaultUI: PropTypes.bool,
  zoomControl: PropTypes.bool,
  scaleControl: PropTypes.bool,
  fullscreenControl: PropTypes.bool,
  gestureHandling: PropTypes.bool,
  onDragStart: PropTypes.func,
  onDragEnd: PropTypes.func,
  showMyLocationButton: PropTypes.bool,
}

GoogleMapView.defaultProps = {
  width: '100%',
  height: '8em',
  lat: -6.291272,
  lng: 106.800752,
  zoom: 16,
  mapRef: () => {},
  disableDefaultUI: false,
  zoomControl: false,
  scaleControl: false,
  fullscreenControl: false,
  gestureHandling: false,
  onDragStart: () => {},
  onDragEnd: () => {},
  showMyLocationButton: false,
}

export default GoogleMapView;
```

## Finally...
Maybe it isn't perfect but it works! :D. We have released it on production right now! This is the demo :

![sayurbox-react-google-map-demo](https://1.bp.blogspot.com/-ZHOPVdB0u5U/Xn7fM_ITJ-I/AAAAAAAAQCQ/hNydLp2knHIUORtRegseevN1ppaFAR7NQCLcBGAsYHQ/s320/sayurbox-google-map-location-picker.gif)

## Closing...
Mmm, with this way I learned something that maybe I'm stuck in the mindset to use existing library to do everything without knowing how it works under the hood, when I tried to build something by myself, it gives me more value and useful experience.

I'm not trying to tell you that we don't have to use any existing library because it's not good too to "re-invent the wheels". But, sometimes, building something from scratch can give us precious experience we haven't meet before. Thanks for reading!