본문 바로가기

TIL

아웃소싱 팀 프로젝트 ⑤ - 모달창에 지도 불러오기

드래그 동작을 막아놓은 피드의 지도부분을 클릭했을 때 큰 모달창이 뜨면서 드래그, 확대축소가 가능한 지도가 출력되도록 하고 싶었다.

 

■ 모달 기본 틀 만들기

이전에 자전거 경로를 선으로 표시한 지도와 동일한 지도를 불러오게 되므로 동일한 코드를 부모 컴포넌트에 옮겨서 함수로 감싼 뒤 피드의 지도, 모달창의 지도 각 컴포넌트에 props로 내려주었다.

지도 생성함수인 showMap을 useEffect 로 감싸서 컴포넌트가 렌더링 되고 실행되도록 하였고 모달 기본 틀을 만들어 주었다.

// Posting.jsx
const Posting = () => {
  const { kakao } = window;
  const [modalOpen, setModalOpen] = useState(false);

  // 지도 생성 함수
  const showMap = (id, roadLine) => {
    const container = document.getElementById(id);
    const options = {
      center: new kakao.maps.LatLng(
        roadLine[Math.floor(roadLine.length / 2)].LINE_XP,
        roadLine[Math.floor(roadLine.length / 2)].LINE_YP
      ),
      level:
        (roadLine.length <= 100 && 8) ||
        (roadLine.length <= 500 && 9) ||
        (roadLine.length >= 1000 && 11) ||
        (roadLine.length >= 3000 && 12),
      draggable: false
    };
   const map = new kakao.maps.Map(container, options);
    const linePath = [];
    roadLine.forEach((el) => linePath.push(new kakao.maps.LatLng(el.LINE_XP, el.LINE_YP)));
    const polyline = new kakao.maps.Polyline({
      path: linePath,
      strokeWeight: 5,
      strokeColor: "#FF54F1",
      strokeOpacity: 0.7,
      strokeStyle: "solid"
    });
    polyline.setMap(map);
  };
  
  return (
    <div>
      {feeds?.length > 0 ? (
        feeds.map((feed) => {
          return (
            <FeedContainder key={feed.id}>
              <RidingMap id={feed.id} roadLine={feed.roadLine} />
              <RidingMap setModalOpen={setModalOpen} showMap={showMap} id={feed.id} roadLine={feed.roadLine} />
              {modalOpen && (
                <ModalMap setModalOpen={setModalOpen} showMap={showMap} id={feed.id} roadLine={feed.roadLine} />
              )}
            </FeedContainder>
          );
        })
      ) : (
        <p>피드가 없습니다.</p>
      )}
   </div>
   )}
// ModalMap.jsx
import { useEffect, useRef } from "react";
import styled from "styled-components";

const ModalMap = ({ showMap, setModalOpen, id, roadLine }) => {
  // showMap : 지도를 생성하는 함수
  useEffect(() => {
    showMap(id, roadLine);
  }, []);
  // 모달창 외부화면 클릭 시 모달 닫기
  const outSection = useRef();
  const closeOutSection = (e) => {
    if (outSection.current === e.target) {
      closeModal();
    }
  };
  const closeModal = () => {
    setModalOpen(false);
  };
  
  return (
    <Layer ref={outSection} onClick={closeOutSection}>
      <Map id={id}>
        <ClostBtn onClick={closeModal}>
          X
        </ClostBtn>
      </Map>
    </Layer>
  );
};

export default ModalMap;

const Layer = styled.div`
  z-index: 500;
  display: block;
  background: rgba(0, 0, 0, 0.3);
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
`;
const ClostBtn = styled.p`
  position: absolute;
  font-size: 30px;
  right: 15px;
  top: 10px;
  color: white;
  cursor: pointer;
`;
const Map = styled.div`
  width: 1200px;
  height: 1000px;
  z-index: 100;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  border: 2px solid white;
`;

 

 

🚨 발생한 문제

지도생성함수를 한 페이지의 RidingMap, ModalMap 두 컴포넌트에서 호출했으나 지도가 한 컴포넌트에서만 출력되는 문제가 발생했다.

 

❗️원인

함수 내부에서 getElementById로 id값을 사용하는데, 두 컴포넌트가 같은 id를 지니고 있어 앞쪽 요소만 지도가 담겼다.

 

✅ 해결방법

id생성방식을 변경하여 다른 id값을 지니게  문제를 해결하였다.

// ModalMap 컴포넌트의 지도 호출
const ModalMap = ({ showMap, id, roadLine }) => {
 useEffect(() => {
  showMap(id + "_map", roadLine);
}, []);

return <Map id={id + "_map"}></Мар>
};

// RidingMap 컴포넌트의 지도 호출
const RidingMap = ({ showMap, id, roadLine }) => {
 useEffect(() => {
  showMap(id, roadLine);
}, []);

return <Map id={id}></Map>;
};

 

 

🚨 발생한 문제

두 컴포넌트에 모두 지도를 불러오지만 피드의 모달을 클릭했을 때 피드 5개씩 동일한 지도를 불러와지는 문제가 발생했다. 

 

❗️원인

feeds의 id, roadLine 값을 모달 컴포넌트에 props로 내려주려고 ModalMap 컴포넌트도 같이 map에 넣어서 렌더링했더니 동일한 지도의 모달이 5개가 생겼다.

 

✅ 해결방법

ModalMap 컴포넌트를 map으로 렌더링하는 중괄호 바깥에다가 옮긴 뒤, modalOpen state 를 없애고 초기값이 빈값인 selectedPost state를 만들어서 props를 내려주었다.

 

 

■ 최종 코드

// Posting.jsx
const Posting = () => {
  const { kakao } = window;
  const [selectedPost, setSelectedPost] = useState();

  // 지도 생성 함수
  const showMap = (id, roadLine) => {
    const container = document.getElementById(id);
    const options = {
      center: new kakao.maps.LatLng(
        roadLine[Math.floor(roadLine.length / 2)].LINE_XP,
        roadLine[Math.floor(roadLine.length / 2)].LINE_YP
      ),
      level:
        (roadLine.length <= 100 && 8) ||
        (roadLine.length <= 500 && 9) ||
        (roadLine.length >= 1000 && 11) ||
        (roadLine.length >= 3000 && 12),
      // 드래그가능 여부를 selectedPost 값 존재여부로 판단
      // 피드지도에서는 드래그가 불가하고 모달창에서는 가능하다.
      draggable: selectedPost ? true : false
    };

    const map = new kakao.maps.Map(container, options);

    const linePath = [];
    roadLine.forEach((el) => linePath.push(new kakao.maps.LatLng(el.LINE_XP, el.LINE_YP)));

    const polyline = new kakao.maps.Polyline({
      path: linePath,
      strokeWeight: 5,
      strokeColor: "#FF54F1",
      strokeOpacity: 0.7,
      strokeStyle: "solid"
    });

    polyline.setMap(map);
  };

  return (
    <div>
      {feeds?.length > 0 ? (
        feeds.map((feed) => {
          return (
            <FeedContainder key={feed.id}>
              <ContentsContainer
                style={{
                  backgroundImage: `linear-gradient(rgba(0, 0, 0, 0.65), rgba(0,0,0,0.65)), url(${feed.profile_img})`
                }}
              >
                <Profile src={feed.profile_img} alt="profile_img" />
                <RiderNameContainer>
                  <RiderName>{feed.nickname}</RiderName>라이더 님
                </RiderNameContainer>
                <RidingRoad>종주점 : {feed.BICYCLE_PATH}</RidingRoad>
                <DetailContainer>
                  <p>종주 일자 : {feed.created_time}</p>
                  <Thumb currentFeedId={feed.id} currentThumb={feed.thumb} thumbUser={feed.userId} />
                </DetailContainer>
              </ContentsContainer>
              <RidingMap setSelectedPost={setSelectedPost} showMap={showMap} id={feed.id} roadLine={feed.roadLine} />
            </FeedContainder>
          );
        })
      ) : (
        <p>피드가 없습니다.</p>
      )}
      {selectedPost && (
        <ModalMap
          setSelectedPost={setSelectedPost}
          showMap={showMap}
          id={selectedPost.id}
          roadLine={selectedPost.roadLine}
        />
      )}
      <div ref={ref} />
    </div>
  );
};

export default Posting;

 

// RidingMap.jsx
const RidingMap = ({ setSelectedPost, showMap, id, roadLine }) => {
  useEffect(() => {
    showMap(id, roadLine);
  }, []);

  const OpenModal = () => {
    // feeds에서 받은 id, roadLine 값을 부모컴포넌트(Positng)에서 해당값을 사용할 수 있도록 setSelectedPost 해줌
    setSelectedPost({ id, roadLine });
  };

  return <Map id={id} onClick={OpenModal}></Map>;
};

// ModalMap.jsx
const ModalMap = ({ showMap, setSelectedPost, id, roadLine }) => {
  useEffect(() => {
    showMap(id + "_map", roadLine);
  }, []);

  const outSection = useRef();

  const closeOutSection = (e) => {
    if (outSection.current === e.target) {
      closeModal();
    }
  };

  const closeModal = () => {
    // setSelectedPost가 빈값일 때 모달이 닫히도록 수정
    setSelectedPost(null);
  };

  return (
    <Layer ref={outSection} onClick={closeOutSection}>
      <Map id={id + "_map"}>
        <ClostBtn onClick={closeModal}>
        X
        </ClostBtn>
      </Map>
    </Layer>
  );
};

 

 

■ 완성 모습

 

 

😢 느낀점

모달 화면에 띄워지지 않는 문제를 구글에 검색했을 때 kakao developers 질문 탭에서 map.relayout(); 을 추가해야한다는 답변을 많이보았다. 해당 메서드를 어떻게 적용해야할 지 고민에 빠져서 지도api 를 여러군데에서 생성할 때 id 값을 다르게 넣어주어야 한다는걸 빨리 알아차리지 못해서 많이 헤맸었다. 그래도 잘 해결해서 기능이 잘 동작하는 모습을 볼 수 있어서 좋았고 외부 api 사용에 대해 많이 배우게 된 시간이었다.