폼 작성 시 주소를 입력하는 부분이 있는데, 이 부분을 단순 텍스트가 아닌 주소 형식의 텍스트로 입력받기 위해 지도 API를 사용한다.
input과 datalist를 사용하기 참고
문제!
input과 select (또는 직접 구현한 div) 사용하기
왼쪽의 장소를 선택하면 오른쪽의 지도에 핀 찍어 주기 시작
지도는 렌더링할 것 하나만 띄우면 된다.
useEffect(() => {
if (mapRef.current?.firstChild) {
return;
}
const map = new Tmapv2.Map('map', {
center: new Tmapv2.LatLng(37.566535, 126.9779692),
zoom: 14,
});
map.setZoomLimit(7, 16);
}, [mapRef]);
마커를 띄워 줄 때는 new Tmapv2.Map()으로 생성한 map 인스턴스가 필요하다. 다음처럼 map 속성에 지도 인스턴스를 넘겨 줄 때 사용하기 때문이다.
const marker = new Tmapv2.Marker({
position: new Tmapv2.LatLng(lonlat.lat(), lonlat.lng()),
map: map,
});
이를 위해서 useEffect 내에서 만들고 있던 new Tmapv2.Map()를 useEffect 바깥으로 꺼냈다.
React Hook useEffect has a missing dependency: 'map'. Either include it or remove the dependency array.
→ dependency 배열에 map 넣음
The 'map' object construction makes the dependencies of useEffect Hook (at line 64) change on every render. Move it inside the useEffect callback. Alternatively, wrap the initialization of 'map' in its own useMemo() Hook.
→ useMemo로 감싸봄
에러 해결됐으나 렌더링 안 되는 문제
Error handled by React Router default ErrorBoundary: TypeError: Cannot read properties of null (reading 'style') at new t (tmapjs2.min.js?version=20230802:1:543099)
→ 다른 방안을 모색하자.
useEffect 내에서 인스턴스 생성할 때 이를 저장해서 사용하자.
const [mapInstance, setMapInstance] = useState<TMapInstance>(null);
useEffect(() => {
if (mapRef.current?.firstChild) {
return;
}
const map = new Tmapv2.Map('map', {
center: new Tmapv2.LatLng(37.566535, 126.9779692),
zoom: 14,
});
map.setZoomLimit(7, 16);
setMapInstance(map);
}, [mapRef]);
렌더링이 무한 루프에 빠졌다
<aside> <img src="/icons/skull_red.svg" alt="/icons/skull_red.svg" width="40px" /> Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn't have a dependency array, or one of the dependencies changes on every render.
</aside>
useEffect(() => {
if (coord.lat && coord.lon) {
currentMarker?.setMap(null);
const position = new Tmapv2.LatLng(coord.lat, coord.lon);
const marker = new Tmapv2.Marker({
position,
map: mapInstance,
});
setCurrentMarker(marker);
mapInstance?.setCenter(position);
}
}, [coord, mapInstance, currentMarker]);
currentMarker
**가 변경되는 조건을 새로 만들어 주어야겠다고 생각해결 방안: useEffect 내부에서 currentMarker와 prevMarker의 위도, 경도 값을 비교해서 변경되었을 경우에만 실행되도록 하자.
const updateMarker = useCallback(
(coord: { latitude: number | null; longitude: number | null }) => {
const { latitude, longitude } = coord;
if (!(latitude && longitude) || !mapInstance) {
return;
}
if (currentMarker) {
const { _lat: prevLatitude, _lng: prevLongitude } =
currentMarker.getPosition();
// 여기서 좌표 값을 비교해서 변경되지 않았으면 로직을 실행하지 않도록 조건을 걸어 주었다
if (prevLatitude === latitude && prevLongitude === longitude) {
return;
}
}
currentMarker?.setMap(null);
const position = new Tmapv2.LatLng(latitude, longitude);
const marker = Marker({
mapContent: mapInstance,
latitude,
longitude,
theme: 'green',
});
setCurrentMarker(marker);
mapInstance?.setCenter(position);
},
[mapInstance, currentMarker],
);
문제 1: 마커를 하나씩만 찍고 싶은데, 여러 개가 찍히는 문제
화면 기록 2023-12-02 21.53.28.mov
T Map 클릭한 위치에 마커 표시하기 예시에 나온 대로
let markers = []
를 사용한 코드는 문제가 없으나, const [markers, setMarkers] = useState([])
를 사용한 방식으로 코드 바꾸면 해당 문제가 발생
왜 이럴까? (ChatGPT의 답변)
useState
**를 사용할 때, markers
배열에 새로운 마커가 추가될 때마다 상태가 업데이트되고 컴포넌트가 리렌더링됩니다. 이로 인해 이전 마커들이 유지되면서 새로운 마커가 추가되는 현상이 발생합니다.let markers = [];
**를 사용하면, 마커 배열이 컴포넌트의 라이프사이클과 독립적으로 존재합니다다. 컴포넌트가 재렌더링될 때마다 **markers
**는 초기화되므로, 항상 마지막에 추가된 마커만 표시됩니다.해결방안: T Map 클릭한 위치에 마커 표시하기 예시 대신 다른 방식을 택함