google mapのマーカーのInfoWindowをreactで書きたい！

# 概要
google mapでマーカーのInfoWindowの中身の記述にReactを使う方法を二つ紹介します

- [react-google-maps](https://github.com/tomchentw/react-google-maps)を使う方法（個人的にはこちらがおすすめかと思います）
- InfoWindowのdomに新しいreactをrenderする方法


# 思い立ったきっかけ
WEBサイトを作るとき位置表示したい時ってgoogle mapを使うことってよくあります。さらに位置表示場所に説明をつけたい時とかはマーカーにInfoWindow使ってポップアップをつけるってのは割とよくある手法じゃないかと思います
ただ、このポップアップの内容はデフォルトだと文字列のhtmlを書いて記述するようにと説明されています。[ここ参照](https://developers.google.com/maps/documentation/javascript/examples/infowindow-simple)

``` sample.js
var contentString =
  '<div id="content">' +
  '<div id="siteNotice">' +
  '</div>' +
  '<h1 id="firstHeading" class="firstHeading">Uluru</h1>' +
  '<div id="bodyContent">' +
  '<p><b>Uluru</b>, also referred to as <b>Ayers Rock</b>, is a large ' +
  'sandstone rock formation in the southern part of the ' +
  'Northern Territory, central Australia. It lies 335&#160;km (208&#160;mi) ' +
  'south west of the nearest large town, Alice Springs; 450&#160;km ' +
  '(280&#160;mi) by road. Kata Tjuta and Uluru are the two major ' +
  'features of the Uluru - Kata Tjuta National Park. Uluru is ' +
  'sacred to the Pitjantjatjara and Yankunytjatjara, the ' +
  'Aboriginal people of the area. It has many springs, waterholes, ' +
  'rock caves and ancient paintings. Uluru is listed as a World ' +
  'Heritage Site.</p>' +
  '<p>Attribution: Uluru, <a href="https://en.wikipedia.org/w/index.php?title=Uluru&oldid=297882194">' +
  'https://en.wikipedia.org/w/index.php?title=Uluru</a> ' +
  '(last visited June 22, 2009).</p>' +
  '</div>' +
  '</div>';
```

これ結構面倒だなって言うのと、特に自分はReactで[MATERIAL-UI](https://material-ui.com/)使ってフロント書くことが多いので他のデザインと差異が出ちゃうことが結構気に食わなかったと言うことがありました


# react-google-mapsを使う方法

まずはreact-google-mapsの紹介です。こちらはそもそもgoogle mapを記述するときにReactの形式で書けると言うものになります。
例えば通常はgoogle mapを使うときには以下のようにhtmlでgoogle mapを読み込み、domに対してgoogle mapを紐付ける記述をjsでする必要がありました

``` sample.html
<script src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&callback=initMap" async defer></script>
```

```sample.js
var map = new google.maps.Map(document.getElementById('map'), {
    zoom: 4,
    center: uluru
});
```

対してreact-google-mapsでは以下のようにReactの中でgoogle mapの読み込みやズーム、初期位置を設定することができます。また、Google Maps JavaScript API v3でできることはだいたいできるそうです。
GoogleMapタグの中にMarkerタグを記述するとマーカーを表示することができ、並列に書けばマーカーを増やしていくこともできます。また、別のコンポーネントにマーカーをまとめて記述して子コンポーネントとして入れることも可能です。

``` sample.jsx
import {
    withScriptjs,
    withGoogleMap,
    GoogleMap,
    Marker,
} from "react-google-maps";

const MapWithAMarker = withScriptjs(withGoogleMap(props =>
    <GoogleMap
        defaultZoom={8}
        defaultCenter={{ lat: -34.397, lng: 150.644 }}
    >
        <Marker
            position={{ lat: -34.397, lng: 150.644 }}
        />
    </GoogleMap>
));

<MapWithAMarker
    googleMapURL="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY"
    loadingElement={<div style={{ height: `100%` }} />}
    containerElement={<div style={{ height: `400px` }} />}
    mapElement={<div style={{ height: `100%` }} />}
/>
```

react-google-mapsでInfoWindowを設定する方法はマーカーのタグの中にさらにInfoWindowタグを入れることで実現できます。

``` sample.jsx
const { compose, withProps, withStateHandlers } = require("recompose");
const FaAnchor = require("react-icons/lib/fa/anchor");
const {
  withScriptjs,
  withGoogleMap,
  GoogleMap,
  Marker,
  InfoWindow,
} = require("react-google-maps");

const MapWithAMakredInfoWindow = compose(
  withStateHandlers(() => ({
    isOpen: false,
  }), {
    onToggleOpen: ({ isOpen }) => () => ({
      isOpen: !isOpen,
    })
  }),
  withScriptjs,
  withGoogleMap
)(props =>
  <GoogleMap
    defaultZoom={8}
    defaultCenter={{ lat: -34.397, lng: 150.644 }}
  >
    <Marker
      position={{ lat: -34.397, lng: 150.644 }}
      onClick={props.onToggleOpen}
    >
      {props.isOpen && <InfoWindow onCloseClick={props.onToggleOpen}>
        <FaAnchor />
      </InfoWindow>}
    </Marker>
  </GoogleMap>
);

<MapWithAMakredInfoWindow
  googleMapURL="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&v=3.exp&libraries=geometry,drawing,places"
  loadingElement={<div style={{ height: `100%` }} />}
  containerElement={<div style={{ height: `400px` }} />}
  mapElement={<div style={{ height: `100%` }} />}
/>
```

InfoWindowの子コンポーネントがInfoWindowのメッセージとなって表示されます。個人的な感覚ではダイアログと同じような感覚で書けば大丈夫と思います。

## Reactの他のgoogle mapライブラリ
google mapをreactでラップしたライブラリは他にもあり、[google-map-react](https://github.com/google-map-react/google-map-react)、[google-maps-react](https://github.com/fullstackreact/google-maps-react)などが自分が調べた限りではありました。
自分はこの三つのうち一番ドキュメントがわかりやすくgithubのスターが多かったreact-google-mapsを使っていましたが、好みなどもあるかもしれませんので合わなかったら他のものを試してみても良いと思います。

# InfoWindowのdomに新しいreactをrenderする方法

二つ目は[ここの記事](https://cuneyt.aliustaoglu.biz/en/using-google-maps-in-react-without-custom-libraries/)を参考にしていますがInfoWindowにidをふったdivタグを作成し、そのidに対してrenderを行うと言う方法です。
この方法で気をつけるべき所はdomreadyでdomが生成された後にrenderしないとうまく行かないと言う所です。生成されてない状態でrenderしても何も表示されずに自分は一度ここでつまづきました。

``` sample.jsx
createInfoWindow(e, map) {
    const infoWindow = new window.google.maps.InfoWindow({
        content: '<div id="infoWindow" />',
        position: { lat: e.latLng.lat(), lng: e.latLng.lng() }
    })
    infoWindow.addListener('domready', e => {
        render(<InfoWindow />, document.getElementById('infoWindow'))
    })
    infoWindow.open(map)
}
```

ちなみにこの方法で`<InfoWindow />`にpropsとしてコールバック関数などを渡すこともできるのでInfoWindowの中でボタンを設置してアクションを起こしたいといったことも可能です。
また、reduxなどでデータ管理を行なっている場合、生成したreactコンポーネントは別と認識されると思っていたのでreduxを更新したい場合も自分はコールバックでreduxのactionを呼び出すといった対応していました。


# まとめ
個人的には初めからコードを書くといった場合にはreact-google-mapsで書いていくのがいいと思います。google mapをReactに組み込めてかけるので全体的にコード量が減りますし再利用もしやすくなる印象です。現在あるコードを使ってやりたいと言う場合はdomを生成する方法でもいいのではないかと思います。

# 余談
react-google-mapsには一点大きな欠点があって中心やズームをjsで動的に変更したい場合、ドキュメントでは`<GoogleMap />`のpropsのcenterやzoomを更新するとあるのですがその中心座標などをreduxで管理してマーカーのactionによって変更していた場合、親コンポーネントが更新される→子コンポーネントが更新される→reduxが更新される→親コンポーネントが更新される・・・と言うループに陥ります。

これを回避する方法は自分が調べた限りだと一つだけありました。
まず、以下のように記述するとthis.mapにmapのオブジェクトが入ります。

``` sample.jsx
<GoogleMap
    defaultZoom={18}
    defaultCenter={{ lat: 35, lng: 137 }}
    ref={map => {
        this.map = map;
    }}
>
    {this.props.children}
</GoogleMap>
```

このオブジェクトの`this.map.context.__SECRET_MAP_DO_NOT_USE_OR_YOU_WILL_BE_FIRED`に`new google.maps.Map`で得られるオブジェクトが入っているようで`this.map.context.__SECRET_MAP_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.setCenter()`とすれば中心を変更することができます。
なのでこのthis.mapをマーカーのコンポーネントに渡しておきマーカーコンポーネントの中で中心座標の更新を行うことで先ほどの問題を解決できます。
ちょっとした小ネタですがこれもつまづいたので書いておこうと思います

# 参考
[react-google-maps](https://github.com/tomchentw/react-google-maps)
[Using Google Maps in React without custom libraries](https://cuneyt.aliustaoglu.biz/en/using-google-maps-in-react-without-custom-libraries/)