Google Haritalar servisinin, React için özelleştirilmiş resmi bir kütüphanesi yok. JS kütüphanesi mevcut. Ancak bu kütüphaneyi direkt React içerisinde React bileşeni gibi kullanabilmek için bazı yöntemlere başvurmamız gerekiyor. Yani direk import { GoogleMap } from 'google-maps' gibi bir kullanım yok. Bu işi yapan bazı resmi olmayan kütüphaneler var, fakat bu yazıda harici kütüphanelere başvurmadan bu işlemleri gerçekleştireceğiz.

Şimdi nedir bu yöntemler onlara bakalım. 2 temel adım var:

  1. Tembel yükleme (lazy loading): DEMO
  2. React bileşenlerini harita içersine entegre etmek: DEMO

1. Tembel yükleme

Aslında eğer javascript dosyalarınızı senkron olarak yüklüyorsanız bu yönteme ihtiyacınız yok. Google kütüphanesini <head> içerisine dahil ederseniz React kodunuz yüklenmeden önce Haritalar kütüphanesi erişebilir durumda olacaktır. Ancak tüm kütüphaneleri senkron olarak yüklerseniz sayfanızın ilk açılışı biraz uzun sürebilir. Bu süre React gibi tek sayfa uygulamaları için pek de problem değildir aslında.

Senkron yüklemek:

<head>
  <script src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY" />
</head>

daha sonra ise React bileşeninden kütüphaneyi çağırmanız yeterli:

class Map extends Component {
  componentDidMount() {
    const map = new window.google.maps.Map(document.getElementById('map'), {
      center: { lat: 41.0082, lng: 28.9784 },
      zoom: 8
    });
  }

  render() {
    return (
      <div style={{ width: 500, height: 500 }} id="map" />
    );
  }
}

Ancak eğer kütüphaneyi tembel olarak yüklemek isterseniz çalışma anında script'i kod içerisine gömmemiz gerekecek:

Şimdi, Map adında bir React bileşeni oluşturalım ve bu bileşenin görevi, eğer Google Maps yüklü değilse önce yüklemek; daha sonra da props değerlerine göre haritayı oluşturmak olsun. Eğer başka bir bileşen kütüphaneyi zaten yüklemiş ise tekrar yüklemesin.

import React, { Component } from 'react';
import { render } from 'react-dom';

class Harita extends Component {
  constructor(props) {
    super(props);
    this.onScriptLoad = this.onScriptLoad.bind(this)
  }

  onScriptLoad() {
    const map = new window.google.maps.Map(
      document.getElementById(this.props.id),
      this.props.options);
    this.props.onMapLoad(map)
  }

  componentDidMount() {
    if (!window.google) {
      var s = document.createElement('script');
      s.type = 'text/javascript';
      s.src = `https://maps.google.com/maps/api/js?key=YOUR_API_KEY`;
      var x = document.getElementsByTagName('script')[0];
      x.parentNode.insertBefore(s, x);
      // Below is important. 
      //We cannot access google.maps until it's finished loading
      s.addEventListener('load', e => {
        this.onScriptLoad()
      })
    } else {
      this.onScriptLoad()
    }
  }

  render() {
    return (
      <div style={{ width: 500, height: 500 }} id={this.props.id} />
    );
  }
}

export default Harita

Şimdi de bir üst bileşenden bu Harita bileşenini çağıralım:
render() {
    return (
      <Harita
        id="myMap"
        options={{
          center: { lat: 41.0082, lng: 28.9784 },
          zoom: 8
        }}
        onMapLoad={map => {
          var marker = new window.google.maps.Marker({
            position: { lat: 41.0082, lng: 28.9784 },
            map: map,
            title: 'Merhaba İstanbul!'
          });
        }}
      />
    );
  }

Şimdi de bu ana dek neler yaptık açıklayalım:

Harita bileşeni bir div etiketi oluşturuyor. Bu etiketin ID'si üst bileşenden geliyor. React, Harita bileşenini oluşturduktan sonra öncelikle window.google objesinin ulaşılabilir olup olmadığını kontrol ediyor. Eğer ulaşılır durumda ise tekrardan yüklemeye çalışmıyor. Ancak kütüphane yüklenmemişse script etiketi oluşturup bu etiketi kod içerisine enjekte ediyor. Bu işlem senkron gerçekleşemez. Yani, bu koddan sonra gelen satırda window.google.maps'e erişmeye çalışırsanız hata alırsınız. Bu yüzden bir dinleyici yani listener eklememiz gerekiyor. Bu dinleyici script eklendikten sonra aktif hale gelecek. Ancak bu durumda google.maps API erişilebilir duruma gelecektir.

Google maps API erişilebilir durumda ancak üst bileşenin bundan haberi yok. Üst bileşeni haberdar etmek için props kullanacağız. onMapLoad'u bu sebeple tanımladık. Harita, onScriptLoad'ı, o da onMapLoad'u çağırıyor ve oluşturulan map nesnesini parametre olarak gönderiyor. onMapLoad çağırıldığında da artık haritaya her işi yaptırabiliriz. Burada örnek olarak Marker oluşturduk. Ancak aklınıza gelebilecek her harita nesnesi oluşturulabilir.

2. React bileşenlerini harita içersine entegre etmek

Google kütüphanesi InfoWindow ve Özel bileşen oluşturmaya izin veriyor ancak bu nesneler HTML metni olarak tanımlı. Yani her türlü HTML markup kullanabilirsiniz ancak oluşturacağınız nesneler React tarafından tanınmayacaktır. Örneğin bir InfoWindow oluşturdunuz. İçerisine bir düğme eklediniz bu düğmeye basıldığında bir Redux fonksiyonunu çalıştırmak istiyorsunuz. Bu pek mümkün gözükmüyor. Ancak çözümü basit. HTML markup ile oluşturulan nesneyi react-dom yardımıyla React bileşenine dönüştürebiliriz. Bu <div id="root" /> etiketine yaptığımız dönüşümden farksız.

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)
  }

  render() {
    return (
      <Harita
        id="myMap"
        options={{
          center: { lat: 41.0082, lng: 28.9784 },
          zoom: 8
        }}
        onMapLoad={map => {
          const marker = new window.google.maps.Marker({
            position: { lat: 41.0082, lng: 28.9784 },
            map: map,
            title: 'Merhaba Istanbul!'
          });
          marker.addListener('click', e => {
            this.createInfoWindow(e, map)
          })
        }}
      />
    );
  }

InfoWindow React bileşeni:

import React from 'react';
import { withStyles } from 'material-ui/styles';
import Card, { CardActions, CardContent, CardMedia } from 'material-ui/Card';
import Button from 'material-ui/Button';
import Typography from 'material-ui/Typography';

const styles = {
card: {
  maxWidth: 345,
},
media: {
  height: 0,
  paddingTop: '56.25%',
},
};

function InfoWindow(props) {
const { classes } = props;
return (
  <div>
    <Card className={classes.card}>
      <CardMedia
        className={classes.media}
image="https://upload.wikimedia.org/wikipedia/commons/thumb/b/b8/Bosphorus.jpg/397px-Bosphorus.jpg"
        title="Contemplative Reptile"
      />
      <CardContent>
        <Typography gutterBottom variant="headline" component="h2">
          Istanbul
        </Typography>
        <Typography component="p">
          Istanbul is a major city in Turkey that straddles Europe and Asia across the Bosphorus Strait. Its Old City reflects cultural influences of the many empires that once ruled here.

        </Typography>
      </CardContent>
      <CardActions>
        <Button size="small" color="primary">
          Share
        </Button>
        <Button size="small" color="primary">
          Learn More
        </Button>
      </CardActions>
    </Card>
  </div>
);
}


export default withStyles(styles)(InfoWindow);

InfoWindow

Bu örneğin çalışabilir haline aşağıdan ulaşabilirsiniz.

https://stackblitz.com/edit/react-rdevxo?file=InfoWindow.js

Sonuç olarak, harita içerisine gömmek istediğimiz her React bileşenini benzer şekilde oluşturup Google Maps'i Reactlaştırabiliriz :) NPM üzerinde bu işlemleri sizin için yapan kütüphaneler var. Projenize göre bu tip bir kütüphane kullanmak faydalı olabilir, ancak bu durumda Google API dökümantasyonunu direk olarak kullanamıyorsunuz ve kütüphaneye bağımlı hale geliyorsunuz. Zaten bu kütüphanelerin kaynak kodlarını alıp incelerseniz bu iki temel adımı kullandıklarını göreceksiniz.