"next": "12.3.1",
react-kakao-maps-sdk": "^1.1.5" 버전 기준으로 작성되었습니다.
{"errorType":"AccessDeniedError","message":"cannot find Authorization : KakaoAK header"}
카카오맵 API 사용 중 콘솔창에 다음과 같은 오류가 출력되었습니다.🤔
// _app.js
function MyApp({ Component, pageProps }) {
const APP_KEY = process.env.NEXT_PUBLIC_KAKAOMAP_APPKEY;
...
return (
<>
<Script src={`https://dapi.kakao.com/v2/maps/sdk.js?appkey=${APP_KEY}&libraries=services&autoload=false`} />
<HeadInfo />
<GlobalStyle />
<ThemeProvider theme={defaultTheme}>
<Component {...pageProps} />
</ThemeProvider>
</>
)
}
APP Key와 Script는 _app.js
에서 로드해 주었습니다. 원래 카카오맵을 사용하는 컴포넌트에서 로드하고자 했는데, 자꾸 window 객체 관련 에러가 떠서 일단 최상위 컴포넌트에서 로드하였습니다. 이 문제도 dynamic import
같은 걸로 해결할 수 있을 것 같은데...🤔 추후에 다시 수정해봐야겠습니다.
import { useState, useEffect, useRef } from 'react';
import Image from 'next/image';
import { Map, MapMarker } from 'react-kakao-maps-sdk';
const MapView = (props) => {
// PARAM state
const [loaded, setLoaded] = useState(false);
const [latitude, setLatitude] = useState0(//지도 위도 좌표); // 지도 위도 좌표
const [longitude, setLongitude] = useState(// 지도 경도 좌표); // 지도 경도 좌표
// PARAM ref (지도 wrapper)
const mapRef = useRef();
// FUNCTION 지도 / 스카이뷰 전환
const setMapType = (maptype) => {
const map = mapRef.current;
const roadmapControl = document.getElementById('btnRoadmap');
const skyviewControl = document.getElementById('btnSkyview');
if (maptype === 'roadmap') {
map.setMapTypeId(kakao.maps.MapTypeId.ROADMAP);
roadmapControl.className = 'selected_btn';
skyviewControl.className = 'btn';
} else {
map.setMapTypeId(kakao.maps.MapTypeId.HYBRID);
skyviewControl.className = 'selected_btn';
roadmapControl.className = 'btn';
}
};
// FUNCTION 지도 확대
const zoomIn = () => {
const map = mapRef.current;
map.setLevel(map.getLevel() - 1);
};
// FUNCTION 지도 축소
const zoomOut = () => {
const map = mapRef.current;
map.setLevel(map.getLevel() + 1);
};
// FUNCTION 좌표 검색
const onSearchAddress = (address) => {
// PARAM 주소-좌표 변환 객체
const geocoder = new kakao.maps.services.Geocoder();
geocoder.addressSearch(address, function (result, status) {
// 정상적으로 검색이 완료됐을 때
if (status === kakao.maps.services.Status.OK) {
const coords = new kakao.maps.LatLng(
result[0].y,
result[0].x
);
setLatitude(coords.Ma);
setLongitude(coords.La);
}
});
};
useEffect(() => {
if (window.kakao) {
const onLoadKakaoMap = () => {
window.kakao.maps.load(() => {
onSearchAddress(props.address);
setLoaded(true);
})
}
onLoadKakaoMap();
}
}, [props.address]);
return (
<>
{
loaded ?
<div id='MapWrapper' className='MapWrapper'>
<Map
center={{ lat: latitude, lng: longitude }}
style={{
width: '100%',
height: '18rem',
position: 'relative',
overflow: 'hidden',
}}
level={3}
ref={mapRef}
>
<MapMarker
className='MapWrapper__map-marker'
position={{ lat: latitude, lng: longitude }}
>
{/* MapMarker의 자식을 넣어줌으로 해당 자식이 InfoWindow로 만들어지게 합니다 */}
{/* 인포윈도우에 표출될 내용으로 HTML 문자열이나 React Component가 가능합니다 */}
</MapMarker>
<div className='MapWrapper__bubble'>
<div className='MapWrapper__bubble-title'>{props.placeName}</div>
<div className='MapWrapper__bubble-link-wrap'>
<a
href={`https://map.kakao.com/link/map/${props.placeName},${latitude},${longitude}`}
className='MapWrapper__bubble-link'
target='_blank'
rel='noreferrer'
>
큰지도보기
</a>
<a
href={`https://map.kakao.com/link/to/${props.placeName},${latitude},${longitude}`}
className='MapWrapper__bubble-link'
target='_blank'
rel='noreferrer'
>
길찾기
</a>
</div>
</div>
</Map>
{/* COMPONENT controller */}
<div className='custom_typecontrol radius_border'>
<span
id='btnRoadmap'
className='selected_btn'
onClick={() => setMapType('roadmap')}
>
지도
</span>
<span
id='btnSkyview'
className='btn'
onClick={() => {
setMapType('skyview');
}}
>
스카이뷰
</span>
</div>
<div className='custom_zoomcontrol radius_border'>
<span onClick={zoomIn}>
<div className='custom_zoomcontrol-icon'>
<Image
src='https://t1.daumcdn.net/localimg/localimages/07/mapapidoc/ico_plus.png'
alt='확대'
layout='fill'
/>
</div>
</span>
<span onClick={zoomOut}>
<div className='custom_zoomcontrol-icon'>
<Image
src='https://t1.daumcdn.net/localimg/localimages/07/mapapidoc/ico_minus.png'
alt='축소'
layout='fill'
/>
</div>
</span>
</div>
</div>
: null
}
</>
);
};
axios
비동기 통신으로 주소 정보를 받아온 뒤, props를 통해 카카오맵 api를 사용하는 MapView
컴포넌트로 주소 정보를 넘겨줍니다.
// FUNCTION 좌표 검색
const onSearchAddress = (address) => {
// PARAM 주소-좌표 변환 객체
const geocoder = new kakao.maps.services.Geocoder();
geocoder.addressSearch(address, function (result, status) {
// 정상적으로 검색이 완료됐을 때
if (status === kakao.maps.services.Status.OK) {
const coords = new kakao.maps.LatLng(
result[0].y,
result[0].x
);
setLatitude(coords.Ma);
setLongitude(coords.La);
}
});
};
주소를 카카오맵으로 출력하려면 위/경도 좌표로 변환해 주어야 하기 때문에, Kakao맵 API의 Geocoder
객체를 사용하는 onSearchAddress
라는 함수를 만들어 주었습니다.
정상적으로 검색이 완료되면 위도와 경도 정보를 반환하는데, 이를 컴포넌트의 state에
연동해 주었습니다.
[카카오맵 API 가이드 - Geocoder]
https://apis.map.kakao.com/web/sample/addr2coord/
useEffect(() => {
if (window.kakao) {
const onLoadKakaoMap = () => {
window.kakao.maps.load(() => {
onSearchAddress(props.address);
setLoaded(true);
})
}
onLoadKakaoMap();
}
}, [props.address]);
...
return (
<>
{
loaded ?
<div id='MapWrapper' className='MapWrapper'>
<Map
center={{ lat: latitude, lng: longitude }}
style={{
width: '100%',
height: '18rem',
position: 'relative',
overflow: 'hidden',
}}
level={3}
ref={mapRef}
>
useEffect
훅을 이용하여 props로 넘겨 준 주소 정보가 변경되면, onSearchAddress
함수를 실행하여 검색된 좌표를 state로 업데이트해 줍니다.
그 뒤 loaded
state를 true로 변경해 주어 지도를 렌더링시켜 주었습니다.
지도가 잘 표시되어서 성공! 인 줄 알았습니다
콘솔창의 400 Bad Request를 보기 전까지 ... 😇
인터넷에 검색을 해 보니, REST API 방식으로 호출하였을 때 400 Bad Request 에러가 많이 뜨는 것 같았는데, 대부분 Authorization: KakaoAK ${REST_API_KEY}
에 올바른 REST API KEY를 넣어 주면 해결되는 것 같았습니다.
일단 Javascript API를 사용하고 있었고, 저는 Javascrpit KEY를 올바르게 넣어 줬기에 위 문제는 아닌 것 같다고 판단되어 계속 검색을 한 결과...🤔
주소 정보에 잘못된 값, 빈 값이 들어있으면 400 Bad Request를 출력합니다.
받아온 주소 정보는 잘못되지 않았는데... 뭔가 이상하다 싶어 다음과 같이 console을 찍어 보았습니다.
useEffect(() => {
if (window.kakao) {
console.log(props.address);
const onLoadKakaoMap = () => {
window.kakao.maps.load(() => {
onSearchAddress(props.address);
setLoaded(true);
})
}
onLoadKakaoMap();
}
}, [props.address]);
그렇습니다,, 비동기로 받아오면서 맨 처음엔 초기 address값을 지정해 주지 않아 undefined 상태였고, 그 후 데이터를 불러오면서 제대로 된 주소로 호출은 되었던.. 모양입니다 😇
useEffect(() => {
if (window.kakao && props.address) {
console.log(props.address);
const onLoadKakaoMap = () => {
window.kakao.maps.load(() => {
onSearchAddress(props.address);
setLoaded(true);
})
}
onLoadKakaoMap();
}
}, [props.address]);
defaultProps
등으로 초기값을 지정해 주는 방법도 있지만 저는 초깃값을 좌표로 따로 설정해 놓은 게 있었기에, 주소 정보가 있을 때만 onSearchAddress()
함수를 실행시키는 방법으로 변경해 보았습니다.
다음과 같이 address 정보가 있을 때만 실행이 잘! 됩니다.
콘솔에 빨간 게 안 보이면 기분이 조크든요 😎