JaeSeoKim / react-kakao-maps-sdk

React components for using kakao map api
https://react-kakao-maps-sdk.jaeseokim.dev
MIT License
284 stars 30 forks source link

CustomOverlayMap를 동적으로 생성하는 방법 문의 #45

Closed joonas-yoon closed 1 year ago

joonas-yoon commented 1 year ago

안녕하세요,

CustomOverlayMap 클래스를 state로 관리했을 때, 동적으로 표시할 수 있는 방법이 있나요?

const [overlays, setOverlays] = useState([
  { key: "marker-1", lat: 37.52897, lng: 126.917101 },
  { key: "marker-2", lat: 37.5268796, lng: 126.919094 },
  { key: "marker-3", lat: 37.527502, lng: 126.920146 },
])

return (
  <Map>
    {overlays.map(({key, lat, lng}) => (
      <CustomOverlayMap
        key={key}
        position={{
          lat,
          lng,
        }}
      >
        <Circle />
      </CustomOverlayMap>
    ))}
  </Map>
)

위 코드는 정상적으로 표시되지만, 다음과 같이 useEffect 로 overlay를 나중에 추가하게 되면 렌더링이 안되고 있어서요.

useEffect(() => {
  setOverlays([
    { key: "marker-1", lat: 37.52897, lng: 126.917101 },
    { key: "marker-2", lat: 37.5268796, lng: 126.919094 },
    { key: "marker-3", lat: 37.527502, lng: 126.920146 },
  ])
}, [])

이 경우에는 Map을 다시 렌더링해야하나요?

코드를 어떻게 수정하면 좋을 지 모르겠습니다.

Expected Actual
image image

Code snippet 아래 링크로 전체 코드를 실행해보실 수 있는데 참고해주세요. https://playcode.io/1277159

joonas-yoon commented 1 year ago

Button을 추가해서 Map 컴포넌트를 다시 그리도록 해도 표시가 되지 않네요 😢

아래와 같이 onCreate 콜백을 등록하면 콘솔에 찍히기는 하는 걸 보면, 객체는 생성되었으나 렌더링되는 부분에서 업데이트가 되지 않는 것 같습니다.

{data.map(({ key, lat, lng }) => (
  <CustomOverlayMap
    key={key}
    onCreate={console.log}
...
JaeSeoKim commented 1 year ago

안녕하세요, 해당 라이브러리는 동적으로 다양한 Overlay를 랜더링 하는 것에 대해서 지원하고 있습니다!

일단 첨부 하신 코드에서 일부 문제점이 발견되어서 해당 부분을 수정 후에 사용하시면 될 것 같습니다.!

ps. 현재 현역 군인 신분이여서 답변이 많이 제한될 수 있습니다. (훈련 및 당직 근무) 전역하고 싶네요…

// App.tsx

…

function MapLoader(
  props: KakaoMapSDK.MapProps | React.CSSProperties
) {
  const [container, setContainer] = useState<React.ReactElement>()

  useEffect(() => {
    const script: Loader = new Loader({
      appkey: 'KAKAO_MAP_SDK_WEB_API_KEY',
      libraries: ["services", "clusterer"],
      retries: 3,
    })
    script.load().then(() => {
      setContainer(<Map {...(props as KakaoMapSDK.MapProps)} />) // 로드가 완료되었을 때 해당 시점에서의 props를 인자로 주어 Map Element를 생성하여 저장하는 것을 볼 수 있습니다..
    })
  }, [setContainer]) // 하지만 해당 Effect의 발생조건은 최초 렌더링 혹은 setContainer가 변경되었을 때 동작되도록 작성이 되어 있습니다.
 // 즉 props의 children 객체가 변경되어도 해당 effect는 다시 발생하지 않고 container 객체가 업데이트가 되는 일이 없기 때문에 리렌더링이 발생하지 않은 것 입니다.

  return <>{container}</>
}

…

가장 좋은 수정 방법으로는 html에 script 태그를 직접 추가 하거나, useInjectKakaoMapApi hook를 사용해주세요. (만약 버그가 있다면 리포트 부탁드립니다.)

joonas-yoon commented 1 year ago

답변 감사합니다. static 하게 html에 직접 script 태그를 추가하기에는 API KEY 를 동적으로 로딩하는 구조를 유지하고 싶어서 어렵습니다 😢 useInjectKakaoMapApi 훅은 한번 살펴볼게요 :)

말씀해주신 부분에서, setContainer로 state를 변경하면 return <>{container}</> 부분이 리렌더링되어서 반영되지 않나요?

그리고 말씀해주신대로라면, effect의 deps에 적당한 props를 추가하면 될 수 있겠는데 매 번 맵을 새로 렌더링하면서 깜박거리지 않을까요?

군복무는 ㅎㅎ 응원하겠습니다.

JaeSeoKim commented 1 year ago

useEffectdeps array에 적당한 값을 넣어서 동작시킬 수도 있지만 그렇게 되면 Loader 객체를 필요 없이 중복되어 계속 생성하는 문제가 발생합니다.

Map를 리렌더링 한다고 하였을 때 내부에서 kakao.Map 객체를 유지하고 추가적으로 변한 Props만 반영하도록 처리되어 있어서, 완벽하게 Rendering Tree에서 제거되지 않는 이상 깜빡임에 대해서는 걱정 하지 않으셔도 됩니다.

useInjectKakaoMapAPI를 사용하지 않고 Loader를 이용하여 사용하신다면 아래와 같이 작성하여 사용하셔도 됩니다.!

// App.tsx

import React, { useEffect, useState, useMemo } from "react"

import KakaoMapSDK, { Map, Loader } from "react-kakao-maps-sdk"
import { CustomOverlayMap } from "react-kakao-maps-sdk"

function MapLoader(
  props: KakaoMapSDK.MapProps | React.CSSProperties
) {

  useEffect(() => {
    const script: Loader = new Loader({
      appkey: 'KAKAO_MAP_SDK_WEB_API_KEY',
      libraries: ["services", "clusterer"],
      retries: 3,
    })
    script.load()
  }, [])

  return <Map {...props} />
}

const Circle = () => (
  <div style={{
    width: '64px',
    height: '64px',
    borderRadius: '50%',
    background: 'dodgerblue',
  }} />
)

export function App(props) {
  const [overlays, setOverlays] = useState([])

  useEffect(() => {
    setOverlays([
      { key: "marker-1", lat: 37.52897, lng: 126.917101 },
      { key: "marker-2", lat: 37.5268796, lng: 126.919094 },
      { key: "marker-3", lat: 37.527502, lng: 126.920146 },
    ])
  }, [])

  return (
    <MapLoader
      center={{
        lat: 37.52897,
        lng: 126.917101,
      }}
      level={3}
      style={{
        width: '1000px',
        height: '540px',
      }}>
      {overlays.map(({ key, lat, lng }) => (
        <CustomOverlayMap
          key={key}
          position={{
            lat,
            lng,
          }}
        >
          <Circle />
        </CustomOverlayMap>
      ))}
    </MapLoader>
  );
}

// Log to console
console.log('Hello console')
joonas-yoon commented 1 year ago

아하.. 내부적으로 Context 관리를 해주고 있었군요?

script load 에 콜백을 사용해야 할 줄 알았는데, 감사합니다 ㅎㅎ

코드 반영해서 확인해보고 다시 말씀드리겠습니다 :>

joonas-yoon commented 1 year ago

문제는 해결되었습니다 감사합니다 ^^

그런데 Loader를 사용하는 것도 playground 에서는 모듈화해도 잘 되는데, 프로젝트에 적용하려니까 다른 부분에서 꼬인 것 같아서 useInjectKakaoMapAPI 를 사용하는 것으로 바꿨습니다 :)

이슈는 닫도록 하겠습니다 😃