지도에서 클릭한 지점 주소 가져오기 | - [네이버 Map's API - Reverse Geocoding] (Next.js/TypeScript)

ain·2022년 11월 25일
10

Naver API

목록 보기
2/2
post-thumbnail

본 게시물에서는 네이버 클라우드 플랫폼 Naver Map's Enterprise API를 사용하여 지도에서 주소를 가져올 수 있는 Reverse Geocoding 구현 방법을 정리하였습니다. 참고문서
사용 스택: Next.js, TypeScript, tailwindCSS

❗️2023년 1월부터 네이버 클라우드 플랫폼 Maps 서비스 중 Reverse Geocoding을 포함하여 일부 서비스에 가격변동있음 공지링크

서론

지도 API를 사용하기로 마음먹은 후부터 API를 이해하고 적용하기까지 시간이 꽤 걸렸다. 네이버 지도 문서를 옛날 것으로 보고 있다거나, 코드를 그냥 가져다 붙혀넣는 둥... 그리고 네이버 맵의 Reverse Geocoding에 대한 API 사용법과 예제가 네이버 클라우드 플랫폼 기술문서에 나와 있긴 하지만 코드에 대한 설명이 한 번에 알아보기 쉽지 않아 글로 정리해보려 한다.

"API를 이해하고 적용하기까지 순서가 있다면 더 접하기 쉬웠을 텐데..." 라고 생각하며 정리해보았다.

1. API 등록하기

지도를 사용하기 위한 API는 NAVER Developers(네이버 로그인, 검색 등등)가 아니라 NAVER Cloud Platform에서 등록해야 한다.

등록한 후 받은 Client Id를 가져와 스크립트를 호출해야 한다. 이 Client Id는 .env파일에 숨겨서 환경변수로 만들어준다.

// .env
CLIENT_ID=myclientid

(등록하는 방법은 메인이 아니기 때문에 따로 정리해두었다. → API 등록하기 정리글)

등록하고 Client Id까지 가져왔다면 API에 대해서 조금 알아가야 할 차례이다. 내가 구현할 코드를 다른 사람이 이미 구현했다고 해서 그 코드를 무턱대고 복사 붙여 넣기를 했다간 더 많은 시간을 낭비할 수 있다.

2. API 이해하기

지도 API는 생성하고 - 설정(커스텀)해주고 - 뿌려주기의 단계를 거친다. 지도를 클릭해서 어떠한 이벤트를 발생시키려면 자바스크립트와 똑같이 Event Listener를 걸어서 그 안에 함수를 넣어주면 된다. 생김새만 다르고 움직이는 건 우리가 해왔던 것과 비슷하다.

이제부터 Reverse Geocoding을 적용하면서 사용한 API의 메서드, 이벤트 그리고 클래스 등을 소개해보겠다.

# naver.maps.Map()

Map 클래스는 애플리케이션에서 지도 인스턴스를 정의합니다. 이 객체를 생성함으로써 개발자는 지정한 DOM 요소에 지도를 삽입할 수 있습니다.
지도 생성 및 기본 동작

new naver.maps.Map(mapDiv, mapOptions)

설명
new 키워드를 사용하여 Map을 생성한다. 매개변수는 두 가지를 받는데 첫 번째에는 지도를 삽입할 HTML 요소의 id를 주고, 두 번째에는 지도를 어떻게 만들지 옵션을 주어야 한다.

Example

// MapSearch.tsx
let nmap = new naver.maps.Map('map', {
  center: new naver.maps.LatLng(myLocation.latitude, myLocation.longitude),
  zoom: 9,
  zoomControl: true,
  size: { width: 500, height: 500 },
});

return (
  <div id='map'></div>
);
  • center: 지도의 어느 곳을 중심으로 둘 것인지 좌표로 전달한다.
  • zoom: 지도의 초기 줌 레벨. 설정하지 않으면 기본값은 11이다.
  • zoomControl: 확대 축소 컨트롤 바를 표시할 것인지 boolean으로 전달한다.
  • size: 지도의 사이즈를 전달해준다. 만약 전달하지 않는다면 지도의 DOM 요소 CSS에 따라 결정되기 때문에 사이는 이 옵션에서 주거나 CSS 중 하나에서 전달해줘야 한다.

더 알아보기 - naver.maps.Map - MapOptions


# naver.maps.LatLng()

LatLng 클래스는 위/경도 좌표를 정의합니다.
Class: naver.maps.LatLng

new naver.maps.LatLng(lat: number, lng: number)

설명

숫자로 된 위도와 경도를 전달해주면 좌표를 객체로 반환한다.
위에서 지도를 생성할 때, 지도의 중심을 정하는 center 옵션에 이 클래스로 좌표 객체를 만들어 전달해주는 것이다.

Example

new naver.maps.LatLng(37.5666103, 126.9783882);

# naver.maps.Event.addListener()

NAVER 지도 API의 이벤트 시스템을 구현합니다. 대상 객체에서 이벤트 알림을 받아 핸들러를 호출하는 리스너를 등록합니다.
StaticObjects: Event

naver.maps.Event.addListener(target, eventName, listener);

설명

자바스크립트의 addEventListener와 같이 지도에서 일어나는 이벤트를 등록하여 줄 때 사용한다. Event에는 addListener 메서드를 제외하고도 clearListener, hasListner 등 이벤트에 관한 메서드들이 많다.
그중에서도 addListener 메서드는 지도 위에서 일어나는 이벤트를 감지하여 함수를 실행한다.

Example

naver.maps.Event.addListener(nmap, 'click', function (e) {
  alert(e.coord); // 클릭한 지점 좌표
});
  • e.coord: listener에서 받아온 event 객체에서 받아올 수 있는 좌표. 지도에서 클릭한 지점의 좌표이다.

더 알아보기 -UI 이벤트


# naver.maps.InfoWindow()

InfoWindow 클래스는 지도 위에 올리는 정보 창을 정의합니다.
Class: naver.maps.InfoWindow

new naver.maps.InfoWindow(options);

설명
new 키워드를 사용하여 정보 창을 생성한다.
아래 사진처럼 주소 말고도 우리가 입력하고 싶은 텍스트를 넣어서 정보창을 띄울 수 있다.

Example

new naver.maps.InfoWindow({ content: '', borderWidth: 0 });

infoWindow.setContent(
  ['<div style="padding:10px;min-width:200px;line-height:150%;>',
   '서울특별시 송파구 잠실동',
   '</div>'
  ].join(''),
);
infoWindow.open(nmap, latlng);
  • content: 정보 창에 들어갈 string 요소이다.
  • borderWidth: 정보 창의 border 굵기. borderWidth 말고도 maxWidth, anchorSkew 등 정보 창을 꾸미는 옵션들이 있다.
  • setContent(): 정보 창의 content를 생성하는 메서드이다. content 옵션이 처음 infoWindow를 생성할 때 필수 요소이기 때문에 처음에 빈문자열로 초기화를 하고 나중에 setContent를 해도 된다.
  • open(map, anchor?): 정보 창이 열렸을 때 이벤트가 발생한다. 첫 번째 매개변수로 지도를 받고, 두 번째로 정보 창을 띄울 위치를 받는다. 위치는 좌표가 될 수도 있고, 마커를 생성하였다면 마커를 전달할 수도 있다.

# naver.maps.Service.geocode

특정 주소의 좌표를 반환하는 geocode API를 호출합니다.
geocode

naver.maps.Service.geocode(options, callback)

먼저, naver.maps.Service는 객체는 NAVER 지도 API v3을 이용해 호출할 수 있는 서버 API들을 메서드로 제공한다. 그 메서드 중 하나인 geocode 메서드는 주소를 전달해주면 좌표를 반환해주는 메서드이다.
첫 번째 매개변수 options 중 하나인 query에 주소를 전달해주면 callback 함수의 매개변수로 status와 response를 받는데, response 객체 안에서 좌표와 내가 검색한 주소의 전체 주소를 받아 볼 수 있다.

Example

naver.maps.Service.geocode(
  {
    query: address, // 주소 전달
  },
  function (status, response) {
    console.log(response); // 응답 객체
  }
);

response 응답객체↓


# naver.maps.Service.reverseGeocode

특정 좌표에 해당하는 주소를 반환하는 reversegeocode API를 호출합니다.

naver.maps.Service.reverseGeocode(options, callback)

geocode와 동작하는 방식은 같지만 reverse 단어 뜻 그대로 geocode의 반대로, 좌표를 받아 주소를 받아오는 방식이다. options 중 coords에 좌표를 전달해주면 response 객체에 주소가 결과로 들어온다. 우리는 지도 클릭 이벤트 안에 이 함수를 넣어서 주소를 가져오고 infoWindow로 띄워줄 예정이다.

Example

naver.maps.Service.reverseGeocode(
    {
      coords: latlng,
      orders: [naver.maps.Service.OrderType.ADDR, naver.maps.Service.OrderType.ROAD_ADDR].join(','),
    },
    function (status, response) {
      console.log(response);
    }
)
  • coords: latlng : latlng은 위에서 언급한 naver.maps.LatLng 메서드로 생성하거나, naver.maps.Event.addListener에서 받아온 event 객체에서 event.coord를 넘겨주면 된다.
  • orders: 좌표에서 어떤 주소로 변환할 것인지 전달한다. 기본값은 법정동과 도로명 주소이다.
  • naver.maps.Service.OrderType.ADDR: 행정동
  • naver.maps.Service.OrderType.ROAD_ADDR: 지번 주소

더보기

본 게시물에서는 지도에서 클릭한 지점의 주소를 정보 창으로 띄우는 것과 , 검색창에 주소를 검색하여 그 주소로 이동하여 정보 창을 띄우는 것을 정리하였기 때문에 API를 전부 자세하게 기재하진 않았다. 다른 정보는 네이버 기술문서에서 참고하면 좋다.

Hello, Naver Maps API - NAVER 지도 API v3




3. API 적용하기

지금부터 API를 적용해 보겠다. 구현할 내용을 다시 한번 정리해보자면,

클릭해서 주소 가져오기

  • 지도 클릭 시 좌표 가져오기 - 좌표를 주소로 변환 - 그 주소를 정보 창에 띄우기

검색해서 주소 가져오기

  • 검색창에 주소 검색 - 주소를 좌표로 변환 - 그 좌표로 지도가 움직이도록 구현 - 해당 주소에 정보 창 띄우기

총 두 가지를 차례대로 구현할 것이다.
API를 사용하여 구현하기 전에 세팅부터 시작해보겠다.

TypeScript와 같이 사용하기

참고문서 - TypeScript 사용

npm i -D @types/navermaps

Naver Map API를 타입스크립트와 같이 쓰고 싶다면 NPM 패키지를 설치하여 NAVER 지도 API 타입 정의 파일을 가져올 수 있다.

const latlng: naver.maps.LatLng = new naver.maps.LatLng(latitude, longitude);

이렇게 패키지가 제공하는 타입을 지정할 수 있다. (any 범벅을 피할 수 있다.)

script 불러오기

참고문서 - 시작하기

<script type="text/javascript" src="https://openapi.map.naver.com/openapi/v3/maps.js?ncpClientId=YOUR_CLIENT_ID"></script>
  • 해당 URL을 script태그에 넣어서 불러와 준다.

  • YOUR_CLIENT_ID 부분에 Application 등록 후 받은 Client Id를 넣어준다.
    여기서 주의할 점은, 네이버 map의 옛날 버전이 아직 존재하여 헷갈릴 수 있다. ncpClientId=~로 넣어줘야 할 부분을 cliendId=~로 넣으면 안 되기 때문에 에러가 뜬다면 한 번쯤 확인해야 할 부분이다.

  • Next.js로 구현하고 있다면 _document.tsx 파일에서 스크립트를 불러와 준다.

// pages/_document.tsx
mport { Html, Head, Main, NextScript } from 'next/document';
import Script from 'next/script';

export default function Document() {
  return (
    <Html lang="ko">
      <Head>
        <Script
          strategy="beforeInteractive"
          type="text/javascript"
          src={`https://openapi.map.naver.com/openapi/v3/maps.js?ncpClientId=${process.env.NEXT_PUBLIC_MAP_KEY}&submodules=geocoder&callback=initMap`}
        ></Script>
      </Head>
      <body>
        <Main />
        <NextScript />
      </body>
    </Html>
  );
}

지도를 가져올 준비를 마쳤다면 이제부터 지도를 만들어 보자!


현재 사용자 위치 가져오기

참고자료 - Naver Map 자유롭게 활용하기 by silverbeen.log
지도를 띄우고 초기 좌표를 사용자의 현재위치로 설정하려면 현재위치를 받아와야 한다. 여기서는 사용자의 현재위치를 여러 곳에서 불러와야 해서 Custom Hook으로 만들어 주었다.

  • useState로 상태 값 만들기
    사용자의 위치를 myLocation이라는 상태 값에 넣을 예정이다. 여기서 초깃값은 빈문자열로, 나중에는 좌표 위/경도를 가져와야 해서 타입은 미리 정해준다. 이 타입은 다른 곳에서도 많이 쓰이기 때문에 때에 맞게 import 할 수 있도록 src/lib/types.ts 파일에 따로 선언해두었다.
// src/lib/types.ts
type Coord = { latitude: number; longitude: number } | '';
// src/components/useGetCurrentLocation.ts
import { Coord } from '../lib/types';

const useGetCurrentLocation = () => {
const [myLocation, setMyLocation] = useState<Coord>('');
}
  • navigator.geolocation.getCurrentPosition()
    자바스크립트 내장 API로 사용자 위치를 가져온다. 성공, 실패 시에 상태 값을 변경해준다.
...
const useGetCurrentLocation = () => {
  const [myLocation, setMyLocation] = useState<Coord>('');
  useEffect(() => {
    if (navigator.geolocation) {
      // 위치 가져오기
      navigator.geolocation.getCurrentPosition(success, error);
    }

    // 성공했다면 상태 값에 사용자 현재 위치 좌표 저장
    // position은 nested Object이기 때문에 [key: string]: any 타입으로 지정해 줌.
    const success = (position: { [key: string]: any }) => {
      setMyLocation({
        latitude: position.coords.latitude,
        longitude: position.coords.longitude,
      });
    }

    // 실패했다면 상태 값에 Default 좌표 저장
    const error = () => {
      setMyLocation({ latitude: 37.3595316, longitude: 127.1052133 });
    }
  }, []);

  // 다른 곳에서 사용할 수 있도록 myLocation을 반환해준다.
  return myLocation;
};

initMap (지도 초기화 하기)

  • 우리는 initMap이라는 함수 안에서 지도를 생성하고 clickEvent를 걸어줄 것이다. clickEvent를 초기에 걸어주지 않으면 이후의 clickEvent도 작동 하지 않는다. 지도는 initMap 안에서 생성하면 다른 함수에서 인자를 전달할 때 사용하지 못하기 때문에 최상위에 먼저 선언을 해준다.

  • 지도를 생성하면 useRef를 사용하여 지도를 참조하는 mapRef의 current에 넣어주고, 이 mapRef와 사용자의 현재 위치인 myLocation을 useEffect 의존배열 안에 넣어준다. 지도가 생성되면

// mapSearch.tsx
const mapRef = useRef<HTMLElement | null>(null);
const myLocation = useGetCurrentLocation();
let nmap: naver.maps.Map;

const initMap = () => {
  if (typeof myLocation !== 'string') {
    nmap = new naver.maps.Map('map', {
      center: new naver.maps.LatLng(myLocation.latitude, myLocation.longitude),
      zoomControl: true,
      size: { width: 500, height: 500 },
  });
  
    naver.maps.Event.addListener(nmap, 'click', function (e) {
      // 좌표에서 주소로 변환하는 함수
      searchCoordinateToAddress(e.coord, nmap as naver.maps.Map);
    });
  }  
};

useEffect(() => {
  initMap();
}, [mapRef, myLocation])

reverseGeocoding (클릭해서 주소 가져오기)

지도 클릭 시 정보 창안에 주소가 뜨도록, clickEvent와 infoWindow API를 사용하는 searchCoordinateToAddress 함수를 만든다.

  • 앞서 API 이해하기에서 언급했던 reverseGeocode 메서드를 사용하여 클릭한 지점의 좌표를 주소로 바꿔준다. initMap 함수에서 전달해주었던 좌표(e.coord)는 options에서 필수로 전달해줘야 하는 coords에 넣어준다.

(여기서 도로명 주소와 지번 주소 둘 다 넣고 싶다면 반복문으로 address 안에 넣으면 됨.)

// src/lib/searchCoordinateToAddress.ts
export const searchCoordinateToAddress = (latlng: naver.maps.LatLng, nmap: naver.maps.Map) => {
  naver.maps.Service.reverseGeocode(
    // options
    {
      coords: latlng,
      orders: [naver.maps.Service.OrderType.ADDR, naver.maps.Service.OrderType.ROAD_ADDR].join(','),
    },
    // callback
    function (
      status: naver.maps.Service.Status,
      response: naver.maps.Service.ReverseGeocodeResponse,
    ) {
      // 응답을 못 받으면 'Something went wrong' alert 띄우기
      if (status !== naver.maps.Service.Status.OK) {
        return alert('Something went wrong!');
      }
       
      // 도로명 주소가 있다면 도로명 주소를, 없다면 지번 주소를 address 변수에 담는다.
      const address = response.v2.address.roadAddress
      ? response.v2.address.roadAddress
      : response.v2.address.jibunAddress;
    },
  );
};

  • 주소를 가져온 후 정보 창을 띄워주기 위해 infoWindow를 생성하고 열어준다.
export const searchCoordinateToAddress = (latlng: naver.maps.LatLng, nmap: naver.maps.Map) => {
  // infoWindow 생성
  const infoWindow = new naver.maps.InfoWindow({ content: '', borderWidth: 0 });
  naver.maps.Service.reverseGeocode(
    {
      coords: latlng,
      orders: [naver.maps.Service.OrderType.ADDR, naver.maps.Service.OrderType.ROAD_ADDR].join(','),
    },
    function (
      status: naver.maps.Service.Status,
      response: naver.maps.Service.ReverseGeocodeResponse,
    ) {
      if (status !== naver.maps.Service.Status.OK) {
        return alert('Something went wrong!');
      }

      const address = response.v2.address.roadAddress
        ? response.v2.address.roadAddress
        : response.v2.address.jibunAddress;

        // infoWindow 안에 넣어줄 html을 setContent 메서드에 넣어준다.
      infoWindow.setContent(
        ['<div style="padding:10px;min-width:200px;line-height:150%;">', address, '</div>'].join('')
      );
        
        // open 메서드에 지도와 좌표를 전달하여 정보 창을 열어준다.
      infoWindow.open(nmap, latlng);
    },
  );
};

geocoding (검색으로 주소 가져오기 및 이동하기)

검색 시 주소로 좌표를 가져오는 함수를 만들기 전에 input과 이벤트를 걸어준다.

1. input 만들기

<form onSubmit={handleSubmit}>
  <input type="text" placeholder="(예:잠실)" />
  <button onClick={handleClick} type="button">
    주소 검색
  </button>
</form>
  • form에는 onSubmit 이벤트를 걸어주고, button에는 onClick 이벤트를 걸어준다.
    이때, button의 type은 기본값이 submit이어서 type을 button으로 해주지 않으면 submit 이벤트가 두 번 일어나기 때문에 type은 button으로 설정해둔다.(예를 들어 사용자가 입력값에 무언가를 잘못 입력했을 때 '주소를 정확히 입력해주세요'라는 문구를 띄운다면, submit 이벤트가 두번 실행되어 이 문구가 두 번 뜬다.)

input에 입력값을 넣고 엔터를 누르면 form이 submit 되어 onSubmit 함수가 실행되니 onSubmit 실행 함수부터 만들어보자.

const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
  e.preventDefault();
  const inputValue = (e.target as HTMLElement).querySelector('input')?.value as string;
  // 주소에서 좌표로 바꿔주는 함수 ↓
  searchAddressToCoordinate(inputValue, nmap);
};
  • form이 submit 되면 페이지가 자동으로 새로고침 되는 기본값이 있어서, 마음에 들지 않는다면 e.preventDefault로 새로고침을 막아준다.
  • 사용자의 입력값을 event 객체에서 가져온 다음 주소에서 좌표로 바꿔주는 함수 searchAddressToCoordinate의 첫 번째 인자로, 지도는 두 번째 인자로 전달해준다.

  • button에 걸린 onClick 이벤트도 비슷한 구조로, 사용자의 입력값을 가져와 searchAddressToCoordinate 함수에 전달해준다.
  const handleClick = (e: React.MouseEvent<HTMLElement>) => {
    const inputValue = ((e.target as HTMLElement).parentNode as HTMLElement).querySelector('input')
      ?.value as string;
    searchAddressToCoordinate(inputValue, nmap);
  };

이렇게 간단하면 좋겠지만 input에는 함정이 있다. 바로 빈값에서 엔터를 치면 submit이 된다는 것. 참고 ↓

When there is only one single-line text input field in a form, the user agent should accept Enter in that field as a request to submit the form.
-HTML 2.0 specification (Section 8.2) - Form Submission

그래서 빈 값일 때도 submit 돼서 새로고침 되는 게 마음에 안 든다면 input을 form 안에 두 개 만들어서 하나는 숨기거나, input의 event.code가 'Enter'일 때 submit을 막아야 한다.

const handleKeydown = (e: React.KeyboardEvent<HTMLInputElement>) => {
  if (e.code === 'Enter' && (e.target as HTMLInputElement).value === '') {
      e.preventDefault();
  }
}

<form onSubmit={handleSubmit}>
  <input onKeyDown={handleKeydown} type="text" placeholder="(예:잠실)" />
  <button onClick={handleClick} type="button">
    주소 검색
  </button>
</form>

2. searchAddressToCoordinate(주소 검색)

주소로 장소를 검색하는 방법은 간단하다. Reverse Geocoding과 비슷하다. 사용자가 입력한 입력값을 넘겨주고, 지도 이동해주는 점만 다르다.

  • 사용자의 입력값(address)과 지도를 똑같이 넘겨받는다.
  • geocode API options에 query로 사용자가 입력한 주소를 넘겨준다.
export function searchAddressToCoordinate(
  address: string,
  nmap: naver.maps.Map,
) {
  naver.maps.Service.geocode(
    {
      query: address,
    },
    function (status: naver.maps.Service.Status, response: naver.maps.Service.GeocodeResponse) {
      // 응답을 못 받으면 'Something went wrong' alert 띄우기
      if (status === naver.maps.Service.Status.ERROR) {
        return alert('Something went Wrong!');
      }
    }
  );
}
naver.maps.Service.geocode(
    {
      query: address,
    },
    function (status: naver.maps.Service.Status, response: naver.maps.Service.GeocodeResponse) {
      if (status === naver.maps.Service.Status.ERROR) {
        return alert('Something went Wrong!');
      }

      // 주소를 도로명으로 찾을 때, 건물명까지 입력하지 않으면 응답받지 못한다.
      if (response.v2.meta.totalCount === 0) {
        return alert('no results');
      }

      const item = response.v2.addresses[0]; // 찾은 주소 정보
      const point = new naver.maps.Point(Number(item.x), Number(item.y)); // 지도에서 이동할 좌표
      const address = item.roadAddress ? item.roadAddress : item.jibunAddress;

      const infoWindow = new naver.maps.InfoWindow({
        content: ['<div style="padding:10px;"><h4>' + address + '</h4></div>'].join(''),
        borderWidth: 0,
      });
      
      // 검색한 주소를 중심으로 지도 움직이기
      nmap.setCenter(point);
      infoWindow.open(nmap, point);
    },
  );
  • 참고로, infoWindow.open(map, anchor) 메서드의 두 번째 인자 anchor 자리에 LatLng 또는 Point 객체가 올 수 있다.

    anchor에 LatLng 또는 Point 객체로 지도 좌표를 설정하면 해당 좌표를 가리키도록 정보 창을 위치시킵니다. 정보 창 Tutorials



결과물

  • 도로명 + 건물명 형식으로 입력하지 않았을 때 화면

  • 알맞은 형식으로 검색했을 시 화면

  • 지도를 클릭했을 시 화면



에러 모음

1. naver is not defined / window is not defined / Cannot read properties of null (reading 'Map')

이슈: naver, window, Map을 못 찾는 경우

해결: 네이버 지도 스크립트를 로드 하기 전에 API를 가져와서 발생하는 경우가 많다.
만약 지도를 비동기 방식으로 로드했다면 스크립트 뒤에 &callback=초기함수이름 을 넣어 지도를 생성할 수 있다.

src=`https://openapi.map.naver.com/openapi/v3/maps.js?ncpClientId=${process.env.NEXT_PUBLIC_MAP_KEY}&submodules=geocoder&callback=initMap`

참고 - NAVER Cloud Platform NAVER Map's Enterprise API - Hello, World

2. 네이버 지도 Open API 인증이 실패했습니다.

이슈: 지도가 렌더링되는 페이지에 진입하여 현재위치 설정 후 지도가 뜨지 않고 인증 실패 화면이 뜬다. 새로고침 하면 지도가 또 제대로 뜬다.

해결: Application을 처음 등록할 때 Web 서비스 URL은 메인 도메인으로 입력해준다. 예를 들어 http://localhost:3000이면 그대로 http://localhost:3000로 등록해준다. 다른 페이지에서 지도를 띄운다고 http://localhost:3000/map이런식으로 등록해주면 위 사진처럼 인증 실패 화면이 뜬다.

또한, script 내의 URL을 clientId가 들어간 옛날 API URL인 "src=https://openapi.map.naver.com/openapi/v3/maps.js?clientId=${MAP_KEY}" 로 했다면, 최신 버전인 ncpClientId가 들어간 src=https://openapi.map.naver.com/openapi/v3/maps.js?ncpClientId=${process.env.NEXT_PUBLIC_MAP_KEY}&submodules=geocoder&callback=initMap로 바꾸어야한다.

참고 - 자주묻는질문: Web Dynamic Map에서 API 인증오류가 납니다. 어떻게 해야 하나요?

3. 지도 겹침 / 중복 / 한 번 이상 렌더링 이슈

이슈: 지도가 한 번만 생성되어야 하는데 한 번 이상 렌더링 되어 겹침 현상. 줌인/줌아웃했을 때 지도 하나는 줌이 되고, 나머지는 줌 컨트롤이 안됨.

해결:

  1. 사용자의 현재 위치를 가져오면 딱 한 번 렌더링 되게 하도록 사용자 위치를 좌표로 담아줄 상태 값(myLocation)을 빈문자열로 초기화해주고, 위치 좌표를 받아 왔을 때 좌표 타입으로 바꿔준다.
const [myLocation, setMyLocation] = useState<{latitude: number, longitude: number} | ''>('');
if(typeof myLocation !== 'string') {지도 생성 및 클릭이벤트 걸어주기}
  1. 지도는 한 번만 생성하기. 여러 컴포넌트에서 써야 한다고 해서 각자 다 생성하면 안 되고 부모 컴포넌트에서 전달해주는 식으로 한번 생성하여 props로 전달한다.
    본인은 (지금 생각해보면 정말 바보 같지만) 클릭해서 주소 찾기 함수에 한번, 검색해서 주소 찾기 함수에 한 번씩 생성했었는데, 주소를 검색한 후에 클릭 이벤트가 안 먹혀서 종일 삽질했었다...

4. button type='submit'

이슈: 주소 검색 시 제출 두 번 되는 이슈

해결: button의 type을 submit으로 하면 인풋에 값이 있는 상태에서 엔터를 눌러 submit을 할 때, 버튼까지 같이 눌려 두번 submit 된다. 그러면 함수도 두번 실행되기 때문에 type은 button으로 수정.

5. input 빈 값일 때 submit 발생

이슈: input이 빈 값이고, 커서가 들어간 상태에서 엔터 클릭 시 submit 됨.

해결: input이 form에 한 개밖에 없을 때는 자동으로 submit이 된다. submit을 막으려면 input을 두 개 만들어 하나는 시각적으로 안 보이게 하거나, onKeyDown 이벤트에 input 값이 빈 문자열일 때 preventDefault()하기.

// first solution
<form>
  <input className='hidden' type='text' />
  <input type='text' />
</form>
// second solution
<form>
  <input onKeyDown={(e: React.KeyboardEvent<HTMLInputElement>) => {
      if (e.code === 'Enter' && (e.target as HTMLInputElement).value === '') {
        e.preventDefault();
      }
    }} type='text' />
</form>

참고

profile
프론트엔드 개발 연습장 ✏️ 📗

0개의 댓글