🗺️

ReactアプリにGoogle Map入れてみた

2023/06/28に公開

ReactアプリにGoogleMap入れようと思って、結構詰まったのでメモメモ

やりたいことは↓のような感じで、マップとそこにマーカーを表示すること。

準備

ライブラリ
公式で@googlemaps/react-wrapperというライブラリが提供されているのでそれを使います。
https://developers.google.com/maps/documentation/javascript/react-map?hl=ja

TypeScriptの人は型定義も必要です。

npm install @googlemaps/react-wrapper @types/google.mapstypes/google.maps

Google Cloud Platform
Google Cloud PlatformでAPIキーを取得する必要があります。
今回やろうとしてることは無料枠で収まります。
(気が向いたらこの手順もまとめます...)
https://developers.google.com/maps/documentation/javascript/cloud-setup?hl=ja

Reactアプリで地図を表示する

基本は上記のチュートリアル通りに進めます。

チュートリアルはform要素でマップを操作するもので、
今回はformを使わないので不要なところがちょこちょこあります。
onClickとかonIdleとかいらないです。

↓できたソースコード↓

App.tsx
const render = (status: Status) => {
  return <h1>{status}</h1>
}

function App() {

  const position = {
    lat: 43.035718552968376,
    lng: 141.46244422700855
  } as google.maps.LatLngLiteral;

  return (
    <div className='flex flex-row w-1/2 justify-center'>
      <Wrapper apiKey='api_key' render={render}>
        <Map style={{ width: '100%', aspectRatio: '16 / 9' }} center={position}/>
      </Wrapper>
    </div>
  )
}

export default App
Map.tsx
type MapProps = google.maps.MapOptions & {
  style: { [key: string]: string };
  children?: React.ReactElement<google.maps.MarkerOptions>[] | React.ReactElement<google.maps.MarkerOptions>;
}

export const Map: React.FC<MapProps> = ({ children, style, ...options }) => {
  const ref = useRef<HTMLDivElement>(null);
  const [map, setMap] = useState<google.maps.Map>();

  useEffect(() => {
    if (ref.current && !map) {
      const option = {
        center: options.center,
        zoom: 16
      };
      setMap(new window.google.maps.Map(ref.current, option));
    }
  }, [ref, map]);

  return (
      <div ref={ref} style={style} />
  );
}

childrenの型はReact.ReactElement<google.maps.MarkerOptions>[] | React.ReactElement<google.maps.MarkerOptions>です。
ここがあってないと後々怒られます。

地図にマーカーを表示させる

ここだよっていう赤いやつを表示させたい。

チュートリアル通りにマーカーコンポーネントを作ります。

Marker.tsx
export const Marker: React.FC<google.maps.MarkerOptions> = (options) => {
  const [markar, setMarkar] = useState<google.maps.Marker>();

  useEffect(() => {
    if (!markar) {
      setMarkar(new google.maps.Marker());
    }

    return () => {
      if (markar) {
        markar.setMap(null);
      }
    };
  }, [markar]);

  useEffect(() => {
    if (markar) {
      markar.setOptions(options);
    }
  }, [markar, options]);

  return null;
}

App.tsxにマーカーコンポーネントを追加します。

App.tsx
  return (
    <div className='flex flex-row w-1/2 justify-center'>
      <Wrapper apiKey='AIzaSyCxI9yUwqIhyjEaElDOCS4eaQzVxFbJ6Tc' render={render}>
        <Map style={{ width: '100%', aspectRatio: '16 / 9' }} center={position}>
+          <Marker position={position} />
        </Map>
      </Wrapper>
    </div>
  )

マップコンポーネントも修正します。
子コンポーネントとしてマーカーを受け取って表示する。

Map.tsx
  return (
    <>
      <div ref={ref} style={style} />
+      {
+        Children.map(children, (child) => {
+          if (isValidElement(child)) {
+            return cloneElement(child, { map });
+          }
+        })
+      }
    </>
  );

このときchildrenの型があってないと、
return cloneElement(child, { map });のmapで怒られます。
(上のほうで後々怒られると書いたやつ)

この呼び出しに一致するオーバーロードはありません。
  前回のオーバーロードにより、次のエラーが発生しました。
    型 '{ map: google.maps.Map | undefined; }' の引数を型 'Partial<unknown> & Attributes' のパラメーターに割り当てることはできません。
      オブジェクト リテラルは既知のプロパティのみ指定できます。'map' は型 'Partial<unknown> & Attributes' に存在しません。ts(2769)

ここまでできると最初の画像のようにマーカー付きの地図ができあがります!

ちなみにこの地図の場所は北都システム本社です

Discussion