89일차 TIL : 아웃소싱 프로젝트 - 카카오맵 검색 결과 지도 범위 유지

변시윤·2023년 1월 27일
0

내일배움캠프 4기

목록 보기
97/131

구현하고자 하는 기능

상세 페이지에서 뒤로 가기 실행시(=리렌더링) 검색 결과에 해당되는 지도 범위 유지하기


에러

검색을 실행하거나 상세페이지에서 뒤로 가기를 실행하면 빈 화면 반환
Uncaught TypeError: a.B is not a function

Main.jsx

const Main = () => {

  const sessionKeyword = sessionStorage.getItem("SearchKeyword");
  const sessionMarkers = JSON.parse(sessionStorage.getItem("SearchMarkers"));
  const sessionBounds = JSON.parse(sessionStorage.getItem("SearchBounds"));

  useEffect(() => {
    const ps = new kakao.maps.services.Places();

    ps.keywordSearch(place, (data, status, _pagination) => {
      if (status === kakao.maps.services.Status.OK) {
        let bounds = new kakao.maps.LatLngBounds();
        let markers = [];

        for (var i = 0; i < data.length; i++) {
          .
          .
          .
          });
          bounds.extend(new kakao.maps.LatLng(data[i].y, data[i].x));
        }

        setMarkers(markers);
        setPlaces(data);
        map.setBounds(bounds);
        displayPagination(_pagination);

        sessionStorage.setItem("SearchKeyword", place);
        sessionStorage.setItem("SearchPlace", JSON.stringify(data));
        sessionStorage.setItem("SearchBounds", JSON.stringify(bounds));
        sessionStorage.setItem("SearchMarkers", JSON.stringify(markers));
      }
    });
  }, [place]);

    useEffect(() => {
    if (!sessionMarkers) return;
    setMarkers(sessionMarkers);
    map.setBounds(sessionBounds);

  }, [sessionKeyword]);


}

원인

두 번째 useEffectmap.setBounds(sessionBounds)


시도한 방법

Map 컴포넌트 예외 처리하기(실패)

  const sessionLat = sessionMarkers ? sessionMarkers[0].position.lat : null;
  const sessionLng = sessionMarkers ? sessionMarkers[0].position.lng : null;
    .
    .
    .
    return (
    <>
        {sessionMarkers ? (
          <Map
            center={{
              lat: sessionLat,
              lng: sessionLng,
            }}
            onCreate={setMap}
          />
        ) : (
          <Map
            center={{
              lat: 37.566826,
              lng: 126.9786567,
            }}
            onCreate={setMap}
           />
        )}
    </>
  );

sessionMarkes의 여부에 따라 sessionMarkes의 좌표 정보 or 기본값을 반환하는 방식이다. 로직 자체는 작동하나 근본적인 해결책은 될 수 없다. 두 가지 문제가 있다.

1. 코드량이 급증한다.
본문에는 생략되었지만 실제로는 <Map /> 안에 훨씬 많은 속성이 있고 또 다른 컴포넌트도 있다.
2. 좁은 범위의 검색만 가능하다.
sessionMarkes(= 검색 결과) 중 첫 번째 결과의 좌표 정보를 반환하기 때문에 넓은 범위에서 검색시 문제가 발생한다. 예를 들면 마포구에 있는 업체의 상세페이지로 들어갔는데 뒤로 가기를 실행하면 송파구에 있는 업체의 좌표가 설정되어 있는 식이다.

세션 스토리지 사용하기(실패)

mount ➡️ setMap ➡️ useEffect 의 순서로 실행되어야 하는데 setMapsetBounds와보다 늦게 실행되거나 혹은 동시에 실행되는 것으로 추측하고 여기서부서 접근하기 시작했다.

import { parse, stringify } from "flatted";

const Main = () => {

  const searchMap = parse(sessionStorage.getItem("SearchMap"));
  .
  .
  .
  useEffect(() => {
    if (!searchMarkers) return;

      setMarkers(searchMarkers);
      searchMap.setBounds(deserialized);

  }, [searchKeyword]);
}

setMap이 실행되지 않더라도 map을 활용할 수 있도록 세션 스토리지에 map 정보를 저장해서 해당 데이터를 활용하고자 했다. 그러나 이번에도 Uncaught TypeError: searchMap.setBounds is not a function을 반환했다.

트러블슈팅 속 트러블슈팅

Converting circular structure to JSON

  sessionStorage.setItem("SearchMap", JSON.stringify(map));
  const searchMap = JSON.parse(sessionStorage.getItem("SearchMap"));

일반적인 세션 스토리지 방법대로 로직을 구현하고 searchMap을 콘솔로 찍어보면 아래와 같은 에러 메시지가 발생한다.

console.log(map);
p {o: {…}, a: div#react-kakao-maps-sdk-map-container, Gh: Array(0), b: Mc, G: wb, …}

잘 보면 객체 앞에 p가 있는데 map이 일반적인 객체 데이터가 아니라 발생한 오류다. yarn add flatted를 설치해서 JSON.stringify, JSON.parse가 아닌 flatted 패키지의 stringify, parse를 사용하면 원하는 형태의 객체 정보를 얻을 수 있다.

null 예외처리 하기(실패)

useEffect 바깥에서 콘솔을 찍어보자 map 정보가 잘 들어오고 있었다. 대신 콘솔이 두 번씩 찍히고 있었는데 첫 번째 결과는 null을 반환했다.

  useEffect(() => {

    if (!searchMarkers) return;

    if (map !== null) {
      setMarkers(searchMarkers);
      map.setBounds(searchBounds);
    }
  }, [map, searchKeyword]);

그래서 null을 예외처리 해봤지만 결과는 Uncaught TypeError: a.B is not a function

map, searchBounds도 콘솔로 찍어보았는데 값이 정상적으로 들어오고 있었다. 그래서 setBounds가 원인이라고 추측을 했다. 결국 또 다시 원점으로...

하지만 결과적으로 null 예외처리가 필요한 건 맞았다. 이것만으로는 해결이 안돼서 그렇지😇

프로토타입 변경하기(성공)

원인은 setBounds가 아니라 searchBounds에 있었다.

  • bounds : 검색 결과에 해당하는 지도 범위
  • searchBounds : bounds 값을 세션 스토리지에 저장한 데이터

같은 데이터를 반환하고 있지만 마지막줄을 보면 프로토타입이 다른 것을 알 수 있다. 바로 이 프로토타입에서 setBounds를 저장하는데 세션 스토리지는 오리지널 프로토타입까지는 저장해주지 않기 때문에 계속해서 같은 에러가 발생했던 것이다.

참고로 프로토타입은 개발자가 직접 생성할 수 있다. setBounds 역시 카카오맵에서 정의한 메서드이며 프로토타입: Y는 카카오맵에서 제공하는 라이브러리이다.

📌 더 알아보기
배열이 아닌 문자형에서 length를 사용할 수 있는 것도 문자형의 프로토타입에 length가 정의되어 있기 때문이다.

  const searchKeyword = sessionStorage.getItem("SearchKeyword");
  const searchMarkers = JSON.parse(sessionStorage.getItem("SearchMarkers"));
  const deserialized = JSON.parse(sessionStorage.getItem("SearchBounds"));

  let searchBounds;

  if (deserialized) {
    searchBounds = Object.setPrototypeOf(
      deserialized,
      kakao.maps.LatLngBounds.prototype
    );
  }

  useEffect(() => {
    if (!searchMarkers) return;
    if (map !== null) {
      setMarkers(searchMarkers);
      map.setBounds(searchBounds);
    }
  }, [map, searchKeyword]);

Object.setPrototypeOf으로 스토리지에 저장했던 bounds 정보를 원래 프로토타입 정보로 변경하면

되는구나... 마침내

Object.setPrototypeOf(object, prototype)

  • object : 프로토타입을 설정할 대상 객체
  • prototype : 프로토타입으로 사용할 객체(null도 가능)

이 문제 해결 하는 데에만 10시간 넘게 소요한 것 같다. 물론 결과적으로는 튜터님의 도움을 받아서 해결했지만 틀린 답조차도 여러 각도에서 접근할 수 있었던 기회였기에 마냥 의미 없는 시간은 아니었다. 그리고 다양한 실패 끝에 얻은 해답이었기에 머릿 속에 더 잘 각인되었다. 프로토타입은 정말 생각치도 못했던 부분이었는데 이 에러 덕분에 두 세 단계쯤은 성장한 것 같다.

해결까지 오랜 시간이 걸렸지만 이 문제 덕분에 그동안 너무나 당연하게 사용해왔기 때문에 단 한 번도 생각해본 적 없던 str.length가 프로토타입 때문이라는 사실을 알았고, 기능 구현하기에 급급해서 튜토리얼을 보면서 따라친 카카오맵 코드도 마침내 이해할 수 있게 되었다.

며칠 전까지만 해도 지도 API 괜히 골랐나 싶었는데 이 문제 해결하고 나서는 선택하길 너무너무 잘했다고 생각하는 중이다. 튜터님이 지도 API 사용해보는 사람이 제일 얻어가는 게 많은 프로젝트라고 하셨었는데 그 말이 사실이었다. 내배캠 시작한 이후로 스스로에게 단 한 번도 만족스러웠던 적이 없었는데 이번 프로젝트는 완성도가 어떻든 간에 스스로에게 최선을 다 했다고 자신있게 말할 수 있을 것 같다.

리얼 개그우먼 닉값 했던 이틀이었다....

profile
개그우먼(개발을 그은성으로 하는 우먼)

0개의 댓글