Skip to content

Instantly share code, notes, and snippets.

@terrierscript
Last active Feb 10, 2019
Embed
What would you like to do?
react-hooks-google-map-part4-A
import { useEffect, useState, useCallback, useReducer, useRef } from "react";
import GoogleMapsApiLoader from "google-maps-api-loader";
// @ts-ignore
import uuid from "uuid/v4";
// Google Mapのオブジェクトを呼び出すだけのhooks
export const useGoogleMap = apiKey => {
const [googleMap, setGoogleMap] = useState(null);
useEffect(() => {
GoogleMapsApiLoader({ apiKey }).then(google => {
setGoogleMap(google);
});
}, []); // useEffectの第二引数を[]にすることで、初回1回目だけ実行される
return googleMap;
};
// 実際にMapをDOMにマウントする処理。
export const useMap = ({ googleMap, mapContainerRef, initialConfig }) => {
const [map, setMap] = useState(null);
useEffect(
() => {
// googleMapかmapContainerRefが初期化されてなければ何もしない
if (!googleMap || !mapContainerRef.current) {
return;
}
const map = new googleMap.maps.Map(
mapContainerRef.current,
initialConfig
);
setMap(map);
},
[googleMap, mapContainerRef]
); // initialConfigは変わったとしても再マウントするとおかしなことになるので更新対象にしない // googleMapかmapContainerRefが変化したらeffectが発火する。
// あとで使えるようにmapを返すようにする
return map;
};
export const useDrawMapMarkers = ({
markers,
googleMap,
map,
onClickMarker
}) => {
const markerObjectsRef = useRef({});
useEffect(
() => {
const { Marker } = googleMap.maps;
markers.map(({ id, position }) => {
// すでに描画済みなmarkerだったら描画しない
if (markerObjectsRef.current[id]) {
return;
}
const markerObj = new Marker({
position,
map,
title: "marker!"
});
// markerがクリックされた時のイベントを追加する
markerObj.addListener("click", e => {
onClickMarker(id, markerObj, markerObjectsRef.current, e);
});
markerObjectsRef.current[id] = markerObj;
});
},
[markers, googleMap, map]
);
};
const markerReducer = (state, action) => {
switch (action.type) {
case "ADD":
const id = uuid();
return {
...state,
[id]: action.payload
};
case "REMOVE":
const { [action.payload]: removeItem, ...rest } = state;
return rest;
default:
return state;
}
};
const mapReducerInitializer = initialMarkers => {
return initialMarkers.reduce((state, marker) => {
return markerReducer(state, {
type: "ADD",
payload: marker
});
}, {});
};
// markerをstate管理する
export const useMarkerState = initialMarkers => {
const [markers, dispatch] = useReducer(
markerReducer,
initialMarkers,
mapReducerInitializer
);
// マーカーの追加・削除のaction関数
const addMarker = useCallback(
position => {
dispatch({
type: "ADD",
payload: position
});
},
[dispatch]
);
const removeMarker = useCallback(
removeUuid => {
dispatch({
type: "REMOVE",
payload: removeUuid
});
},
[dispatch]
);
// 外向けにはobjectではなくarrayとして返す
const getMarkers = useCallback(
() => {
return Object.entries(markers).map(([id, position]) => ({
id,
position
}));
},
[markers]
);
return {
// markers // objectとしてのmarkerは隠蔽する
addMarker,
removeMarker,
getMarkers
};
};
// Mapがクリックされたらイベントを飛ばすhooks
export const useMapClickEvent = ({ onClickMap, googleMap, map }) => {
useEffect(
() => {
const listener = googleMap.maps.event.addListener(map, "click", e => {
console.log(e);
onClickMap({
lat: e.latLng.lat(),
lng: e.latLng.lng()
});
});
// onClickMapが変更されたらつくったイベントをクリアする
//(じゃないとクリックするたびにイベントが大量閣下さえる)
return () => {
googleMap.maps.event.removeListener(listener);
};
},
[googleMap, map, onClickMap]
);
};
import React, { useRef } from "react";
import ReactDOM from "react-dom";
import {
useGoogleMap,
useMap,
useDrawMapMarkers,
useMarkerState,
useMapClickEvent
} from "./hooks";
import styled from "styled-components";
const API_KEY = undefined;
const initialConfig = {
zoom: 12,
center: { lat: 35.6432027, lng: 139.6729435 }
};
const initialMarkers = [
{ lat: 35.6432027, lng: 139.6729435 },
{ lat: 35.5279833, lng: 139.6989209 },
{ lat: 35.6563623, lng: 139.7215211 },
{ lat: 35.6167531, lng: 139.5469376 },
{ lat: 35.6950961, lng: 139.5037899 }
];
const MapContainer = styled.div`
height: 100vh;
width: 100%;
`;
const useMapMarkerSetup = ({ googleMap, map }) => {
// stateとして管理するマーカー
const { addMarker, removeMarker, getMarkers } = useMarkerState(
initialMarkers
);
const markers = getMarkers();
// 描画する
useDrawMapMarkers({
markers,
googleMap,
map,
// 削除イベントを追加
onClickMarker: (id, markerObj, markerObjectsRef) => {
removeMarker(id);
markerObj.setMap(null);
markerObjectsRef[id] = null;
}
});
// クリックイベントを追加
useMapClickEvent({
onClickMap: ({ lat, lng }) => {
addMarker({ lat, lng });
},
map,
googleMap
});
};
const MapMarkers = ({ googleMap, map }) => {
useMapMarkerSetup({ googleMap, map });
return null;
};
const WaitForMap = ({ googleMap, map, children }) => {
if (!googleMap || !map) {
return null;
}
return children;
};
export const MapApp = () => {
const googleMap = useGoogleMap(API_KEY);
const mapContainerRef = useRef(null);
const map = useMap({
googleMap,
mapContainerRef,
initialConfig
});
return (
<>
<MapContainer ref={mapContainerRef} />
<WaitForMap googleMap={googleMap} map={map}>
<MapMarkers googleMap={googleMap} map={map} />
</WaitForMap>
</>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(<MapApp />, rootElement);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment