우리 조의 메인 기능은 지도 위에 산책로를 그리면 그 거리에 맞게 예상 시간과 총 거리가 계산되어 사용자에게 알려주어야한다.
api 는 카카오 지도를 사용하기로 했고 그 중 선 거리 구하기 api
를 사용하려고 했는데, 다 만족스러운 기능이지만 직선밖에 되지 않는게 계속 아쉬웠다.😭 그러던 중 Drawing 라이브러리
를 발견했고 두 api를 잘 섞으면 곡선은 아닐지라도 조금은 섬세한 도형을 그릴 수 있을 것 같았다.
내가 간략하게 만든 코드와 선 거리 구하기로 구현한 코드를 합쳐야했는데 비슷한 메소드를 쓴 듯 하면서도 달라서 머리가 터지는 줄 알았고 2-3번 갈아엎었다😭
여러 이슈들이 있었지만 이번에 Zustand
로 만든 상태를 가져와서 사용하는 과정에서 있었던 트러블 슈팅을 정리해보려한다.
팀프로젝트 트러블슈팅(무한루프, 데이터 undefined)
import { useEffect, useState, useRef } from "react";
import {
CustomOverlayMap,
Map,
Polyline,
DrawingManager,
} from "react-kakao-maps-sdk";
import routeDataStore from "../zustand/routeDataStore";
const WalkPath = () => {
const managerRef = useRef(null);
const [paths, setPaths] = useState([]);
const [distance, setDistance] = useState(0);
const [isDrawingComplete, setIsDrawingComplete] = useState(false);
const [isdrawing, setIsdrawing] = useState(false);
const [mousePosition, setMousePosition] = useState({
lat: 0,
lng: 0,
});
const setRouteData = routeDataStore((state) => state.setRouteData);
const routeData = routeDataStore((state) => state.routeData);
useEffect(() => {
console.log("거리", distance);
console.log("루트데이터", routeData);
}, [distance, routeData]);
// 선 그리기 모드 선택 함수
function selectOverlay() {
const manager = managerRef.current;
manager.cancel(); // 그리기 중이던 모든 도형을 취소
manager.select(kakao.maps.drawing.OverlayType.POLYLINE); // 선 그리기 모드 선택
}
// 선 그리기 완료 처리
const handleDrawComplete = () => {
const manager = managerRef.current;
const overlayData = manager.getData();
if (overlayData.polyline.length > 0) {
const points = overlayData.polyline[0].points;
const path = points.map((point) => ({
lat: point.y,
lng: point.x,
}));
// Polyline을 이용한 총 거리 계산
const calculatedDistance = calculateTotalDistance(path);
setPaths(path);
setDistance(calculatedDistance);
setIsDrawingComplete(true);
console.log("경로:", path);
console.log("총 거리:", calculatedDistance);
}
};
// Polyline을 이용한 총 거리 계산 함수
const calculateTotalDistance = (path) => {
if (path.length < 2) return 0;
const polyline = new window.kakao.maps.Polyline({
path: path.map(
(point) => new window.kakao.maps.LatLng(point.lat, point.lng)
),
});
const totalDistance = polyline.getLength();
return totalDistance;
};
//🚨 무한루프가 발생한 원인
setRouteData({
paths,
totalDistance: distance,
totalWalkkTime: (distance / 67) | 0,
});
console.log("루트데이터", setRouteData);**
return (
{/* return문 코드 생략 */}
);
};
export default WalkPath;
setRouteData
를 WalkPath
컴포넌트 안에서 바로 호출하고 있기 때문이었다. setRouteData
가 상태를 업데이트하면 컴포넌트가 다시 렌더링되고, 다시 setRouteData
가 호출되어 무한 루프가 발생하는 상황이다.
이를 해결하려면, setRouteData
를 useEffect
내부에서 호출하여 distance
나 paths
가 변경될 때만 호출되도록 수정했다. 이렇게 하면 상태가 변할 때만 setRouteData
가 호출되고, 무한루프가 방지된다.
// distance나 paths가 변경될 때만 setRouteData를 호출
useEffect(() => {
if (paths.length > 0 && distance > 0) {
**setRouteData({
paths,
totalDistance: distance,
totalWalkkTime: (distance / 67) | 0,
});
console.log("루트데이터 설정 완료", paths, distance);**
}
}, **[paths, distance, setRouteData]);**
path
, distance
가 변경될 때만 실행되도록 의존성 배열 추가
콘솔에 거리 데이터는 잘 담기는데 폼 작성 후 제출하면 db.json에 posiiton을 제외한 다른 데이터는 저장이 되지 않는게 큰 문제였다..!!!🤯
SaveUserRouteInfo
컴포넌트에서 json-server에 저장할 때, routeFormData
는 잘 전달되지 않아서 거리
만 저장되는 상황이라고 생각했다.
import { create } from "zustand";
const userRouteStore = create((set) => ({
routeFormData: {
routeName: "",
address: "",
description: "",
selectedPuppy: "default"
},
setUserRouteData: (data) => set((state) => ({ routeFormData: { ...state.routeFormData, ...data } }))
}));
export default userRouteStore;
import { useEffect, useState, useRef } from "react";
import { CustomOverlayMap, Map, Polyline, DrawingManager } from "react-kakao-maps-sdk";
import routeDataStore from "../zustand/routeDataStore";
const WalkPath = () => {
const managerRef = useRef(null);
const [paths, setPaths] = useState([]);
const [distance, setDistance] = useState(0);
const [isDrawingComplete, setIsDrawingComplete] = useState(false);
const [isdrawing, setIsdrawing] = useState(false);
const [mousePosition, setMousePosition] = useState({
lat: 0,
lng: 0,
});
const setRouteData = routeDataStore((state) => state.setRouteData);
const routeData = routeDataStore((state) => state.routeData);
useEffect(() => {
console.log("거리", distance);
console.log("루트데이터", routeData);
}, [distance, routeData]);
// distance나 paths가 변경될 때만 setRouteData를 호출
useEffect(() => {
if (paths.length > 0 && distance > 0) {
setRouteData({
paths,
totalDistance: distance,
totalWalkkTime: (distance / 67) | 0,
});
console.log("루트데이터 설정 완료", paths, distance);
}
}, [paths, distance, setRouteData]);
// 선 그리기 모드 선택 함수
function selectOverlay() {
const manager = managerRef.current;
manager.cancel(); // 그리기 중이던 모든 도형을 취소
manager.select(kakao.maps.drawing.OverlayType.POLYLINE); // 선 그리기 모드 선택
}
// 선 그리기 완료 처리
const handleDrawComplete = () => {
const manager = managerRef.current;
const overlayData = manager.getData();
if (overlayData.polyline.length > 0) {
const points = overlayData.polyline[0].points;
const path = points.map((point) => ({
lat: point.y,
lng: point.x,
}));
// Polyline을 이용한 총 거리 계산
const calculatedDistance = calculateTotalDistance(path);
setPaths(path);
setDistance(calculatedDistance);
setIsDrawingComplete(true);
console.log("경로:", path);
console.log("총 거리:", calculatedDistance);
}
};
// Polyline을 이용한 총 거리 계산 함수
const calculateTotalDistance = (path) => {
if (path.length < 2) return 0;
const polyline = new window.kakao.maps.Polyline({
path: path.map((point) => new window.kakao.maps.LatLng(point.lat, point.lng)),
});
const totalDistance = polyline.getLength();
return totalDistance;
};
return (
{/* return문 코드 생략 */}
)
};
export default WalkPath;
import { useState } from "react";
import userRouteStore from "../zustand/userRouteStore";
import routeDataStore from "../zustand/routeDataStore";
import { createRouteInfo } from "../api/pathDataSave";
const SaveUserRouteInfo = () => {
const routeName = userRouteStore((state) => state.routeName);
const address = userRouteStore((state) => state.address);
const description = userRouteStore((state) => state.description);
const selectedPuppy = userRouteStore((state) => state.selectedPuppy);
const routeFormData = userRouteStore((state) => state.routeFormData);
const setUserRouteData = userRouteStore((state) => state.setUserRouteData);
const routeData = routeDataStore((state) => state.routeData);
const handleRouteName = (e) => {
setUserRouteData((prev) => ({ ...prev, routeName: e.target.value }));
};
const handleSetAddress = (e) => {
setUserRouteData((prev) => ({ ...prev, address: e.target.value }));
};
const handleDescription = (e) => {
setUserRouteData((prev) => ({ ...prev, description: e.target.value }));
};
const handleSelectPuppy = (e) => {
setUserRouteData((prev) => ({ ...prev, selectedPuppy: e.target.value }));
};
const handleRouteFormSubmit = async (e) => {
e.preventDefault();
const userRouteAllDate = {
...routeFormData,
...routeData
};
try {
await createRouteInfo(userRouteAllDate);
alert("루트정보저장완료!");
console.log("루트정보저장완료", userRouteAllDate);
} catch (error) {
console.error("루트정보저장에러", error);
}
};
return (
{/* return문 코드 생략 */}
);
};
export default SaveUserRouteInfo;
const SaveUserRouteInfo = () => {
// store에서 불러온 상태들
const routeName = userRouteStore((state) => state.routeName);
const address = userRouteStore((state) => state.address);
const description = userRouteStore((state) => state.description);
const selectedPuppy = userRouteStore((state) => state.selectedPuppy);
const setUserRouteData = userRouteStore((state) => state.setUserRouteData);
const routeData = routeDataStore((state) => state.routeData);
// routeDataStore에서 경로 데이터 가져오기
// ... //
// 모든 입력 데이터를 하나로 통합하여 서버에 전송
const userRouteAllData = {
routeName,
address,
description,
selectedPuppy,
...routeData, // routeDataStore에서 가져온 경로 정보 추가
};
: routeName
, address
, description
등 사용자 입력 값과 routeDataStore
의 데이터를 합쳐 userRouteAllData
에 병합하여 서버에 전송
상태를 업데이트할 때 setUserRouteData
가 기존 값들을 덮어쓰지 않고 각각의 입력 값만 업데이트하도록 수정
Uncaught TypeError: Cannot read properties of null
(reading 'totalDistance') - SaveUserRouteInfo.jsx:74
The above error occurred in the <SaveUserRouteInfo> component:
at SaveUserRouteInfo (<http://localhost:5173/src/components/
SaveUserRouteInfo.jsx?t=1726486624666:22:21>)
routeData
가 null 또는 undefined인 경우를 처리하는 로직을 추가하여 컴포넌트가 안전하게 렌더링되도록 수정하여 처리하기로 했다.
const routeData = routeDataStore((state) => state.routeData) || {};
// 초기값을 빈 객체로 설정
콘솔 에러를 해결했더니 다른 문제가 생겼는데 이 글을 쓰게 된 이유이다.ㅋㅋㅋㅋ
경로는 다 뜨고 나머지는 전부 제출 버튼을 눌렀을 때 undefined 가 뜨는 오류 발생했다
사용자가 입력한 데이터가 userRouteStore
의 상태에 제대로 반영되지 않기 때문인데 해결 방법이 도무지 생각나지 않았다.
Zustand 상태 불러오기 방식 수정: Zustand의 상태를 가져올 때, 기존처럼 개별적으로 routeName
, address
등을 가져오는 대신, 하나의 객체
로 가져오도록 수정
// 🚨 개별적으로 가져왔던 코드
const SaveUserRouteInfo = () => {
// 주스탠드 store에서 불러온 상태들
const routeName = userRouteStore((state) => state.routeName);
const address = userRouteStore((state) => state.address);
const description = userRouteStore((state) => state.description);
const selectedPuppy = userRouteStore((state) => state.selectedPuppy);
const setUserRouteData = userRouteStore((state) => state.setUserRouteData);
const routeData = routeDataStore((state) => state.routeData);
// ✅ 객체로 담아서 가져오는 버전으로 수정
const SaveUserRouteInfo = () => {
const { routeName, address, description, selectedPuppy, setUserRouteData } = userRouteStore(
(state) => ({
routeName: state.routeFormData.routeName,
address: state.routeFormData.address,
description: state.routeFormData.description,
selectedPuppy: state.routeFormData.selectedPuppy,
setUserRouteData: state.setUserRouteData,
})
);
userRouteStore((state) => state.routeName)
으로 접근하도록 만들었다. 그런데 상태 관리(Zustand)에서 이 필드들은 routeFormData
라는 객체 안에 들어있기때문에. routeName
, address
같은 값들은 state
의 최상위에 없고, state.routeFormData
안에 있어서 undefined
가 나왔던 것이다.
코드를 객체로 바꾸지 않고 가져오려면 이렇게 작성해주었어야했다.
const routeName = userRouteStore((state) => state.**routeFormData**.routeName);
const address = userRouteStore((state) => state.**routeFormData**.address);
const description = userRouteStore((state) => state.**routeFormData**.description);
const selectedPuppy = userRouteStore((state) => state.**routeFormData**.selectedPuppy);
const routeName = userRouteStore((state) => state.routeName);
const address = userRouteStore((state) => state.address);
const description = userRouteStore((state) => state.description);
const selectedPuppy = userRouteStore((state) => state.selectedPuppy);
const setUserRouteData = userRouteStore((state) => state.setUserRouteData);
이번 과제를 통해서 Zustand
도 써보고 api
도 써보면서 경험치는 쌓은 것 같은데 아직도 헷갈리는 부분이 많고 이게 맞나? 쓰면서도 제대로 쓰고있는게 맞나? 생각이 들었다..ㅋㅋㅋ 우선 발표가 끝나면 코드들을 다시 보면서 긴가민가했던 부분들을 튜터님들께 맞는지 확인해봐야할 것 같다.