Mapbox 지도 API를 활용해 개인 프로젝트를 진행했습니다.
지도 상 특정 위치에 핀을 꽂고 해당 위치 정보를 활용해 애플리케이션 아이템을 구현하는 과정은 몹시 즐거웠습니다.
하지만 무엇보다 프로젝트를 마치고 가장 기억에 남는 점은 팝업이 나타나면서 깜빡거리는 현상을 개선한 트러블슈팅 과정입니다. 🚀
문제가 발생하는 과정을 논리적으로 정리하고 어느 부분을 손 봐야 하는지 진단하는 과정이 인상적이었기 때문입니다.
지도 상 특정 위치를 더블 클릭하면 해당 위치에 대한 정보를 입력하는 팝업이 나타납니다.
팝업이 정상적으로 나타났으면 좋았겠지만... 깜빡거리면서 등장하는 문제가 발생했습니다. 😭
이는 유저 경험에 치명적일 수 있기 때문에 문제 해결을 위해 고민하는 시간을 가졌습니다.
팝업이 깜빡이면서 등장하는 문제를 해결하기 위해 우선 문제 상황을 정의했습니다.
문제 상황을 아래와 같이 3가지 단계로 정리했습니다.
문제 상황을 구체적으로 살펴보기 위해 코드 상 4가지 값에 주목했습니다.
function App() {
// 지도 뷰 상태 정보
const [viewState, setViewState] = useState<ViewState | null>({
bearing: 0,
longitude: 2.294694,
latitude: 48.858093,
padding: {
top: 0,
bottom: 0,
left: 0,
right: 0,
},
pitch: 0,
zoom: 4,
});
... 💻 MORE STATE ...
// ✅ 새로운 좌표 정보
const [newCoordinate, setNewCoordinate] = useState<NewCoordinate | null>(
null
);
// ✅ 지도 더블 클릭 이벤트 핸들러
const handleMapDbClick = useCallback(
(event: mapboxgl.MapLayerMouseEvent) => {
event.preventDefault();
if (!currentUser) {
signinAlertRef.current?.showModal();
} else {
const { lng, lat } = event.lngLat;
setCurrentPin(null);
setNewCoordinate({ lng, lat });
}
},
[currentUser]
);
// ✅ 새로운 좌표의 상태값(newCoordinate )이 업데이트 되면 실행되는 useEFfect 훅
useEffect(() => {
if (!newCoordinate) {
return;
} else {
setViewState(prev => ({
...prev!,
longitude: newCoordinate.lng,
latitude: newCoordinate.lat,
}));
}
}, [newCoordinate]);
return (
<Map
{...viewState}
cursor="pointer"
style={{ width: "100vw", height: "100vh" }}
mapStyle="mapbox://styles/mapbox/streets-v9"
onMove={event => setViewState(event.viewState)}
onDblClick={handleMapDbClick}
mapboxAccessToken={process.env.REACT_APP_MAPBOX_ACCESS_TOKEN}
>
... 💻 MORE CODE ...
// ✅ 새로운 좌표의 상태값(newCoordinate) 여부에 따라 조건부 렌더링
{newCoordinate && (
<NewPinPlaceInfo
currentUser={currentUser}
newCoordinate={newCoordinate}
handlePins={setPins}
handleNewCoordinate={setNewCoordinate}
/>
)}
... 💻 MORE CODE ...
</Map>
);
}
export default App;
다음으로는 4가지 값이 상호작용하는 과정을 구체적으로 살펴봤습니다.
결과적으로, 문제의 원인은 업데이트 되는 좌표(newCoordinate)의 상태값이었습니다.
이제 문제가 되는 로직을 구체적으로 살펴보겠습니다.
컴포넌트가 리렌더링 되면서, 즉 팝업이 화면에 두 차례 등장하면서 팝업이 깜빡이는 것이었습니다.
문제를 해결하기 위해 새로운 상태값 showNewPin을 추가했습니다.
const [showNewPin, setShowNewPin] = useState(false);
새롭게 추가한 상태값은 조건부 렌더링을 위해 사용됩니다.
수정된 조건부 렌더링 방식에 의해 팝업은 화면에 한 번만 등장합니다.
(팝업을 보여주기 위해서는 showNewPin 값을 true로 바꿔야 합니다.)
기존 코드에서 아래 2가지 부분을 수정했습니다.
function App() {
// 새로운 핀 좌표 정보
const [newCoordinate, setNewCoordinate] = useState<NewCoordinate | null>(
null
);
// 🟠 새롭게 추가한 상태
const [showNewPin, setShowNewPin] = useState(false);
... 💻 MORE CODE ...
const handleMapDbClick = useCallback(
(event: mapboxgl.MapLayerMouseEvent) => {
event.preventDefault();
if (!currentUser) {
signinAlertRef.current?.showModal();
} else {
const { lng, lat } = event.lngLat;
setCurrentPin(null);
setNewCoordinate({ lng, lat });
}
},
[currentUser]
);
... 💻 MORE CODE ...
useEffect(() => {
if (!newCoordinate) {
return;
} else {
setShowNewPin(true);
setViewState(prev => ({
...prev!,
longitude: newCoordinate.lng,
latitude: newCoordinate.lat,
}));
}
}, [newCoordinate]);
return (
<Map
{...viewState}
cursor="pointer"
style={{ width: "100vw", height: "100vh" }}
mapStyle="mapbox://styles/mapbox/streets-v9"
onMove={event => setViewState(event.viewState)}
onDblClick={handleMapDbClick}
mapboxAccessToken={process.env.REACT_APP_MAPBOX_ACCESS_TOKEN}
>
... 💻 MORE CODE ...
// 🟠 수정한 부분
{newCoordinate && showNewPin && (
<NewPinPlaceInfo
currentUser={currentUser}
newCoordinate={newCoordinate}
handlePins={setPins}
handleNewCoordinate={setNewCoordinate}
handleShowNewPin={setShowNewPin}
/>
)}
... 💻 MORE CODE ...
</Map>
);
}
export default App;
새로운 부분을 추가하면서 기존의 과정을 수정했습니다.
1번 과정까지는 이전과 동일합니다.
showNewPin 상태를 추가하면서 팝업을 화면에 한 번만 렌더링 되게 로직을 수정했습니다.
팝업이 등장하면서 더이상 깜빡이지 않는 것을 확인할 수 있습니다!! 👏🏻👏🏻🎉
문제를 해결하는 과정이 깔끔하고 신속했습니다.
이번의 경험을 잊지 않고 추후 발생하는 트러블슈팅 과정에서 참고해야겠습니다. 😆
추가로적으로, 해당 이슈를 해결하면서
1) useEffect 훅의 동작 방식을 다시 한번 자세하게 살펴보는,
2) useState 대신 useRef를 활용하는 등 애플리케이션 성능 개선에 대해 고민하는
의미있는 시간 또한 가질 수 있었습니다.