리액트 심화 팀프로젝트 트러블슈팅

하영·2024년 9월 22일
1

팀프로젝트

목록 보기
9/27
post-thumbnail

우리 조의 메인 기능은 지도 위에 산책로를 그리면 그 거리에 맞게 예상 시간과 총 거리가 계산되어 사용자에게 알려주어야한다.

api 는 카카오 지도를 사용하기로 했고 그 중 선 거리 구하기 api 를 사용하려고 했는데, 다 만족스러운 기능이지만 직선밖에 되지 않는게 계속 아쉬웠다.😭 그러던 중 Drawing 라이브러리를 발견했고 두 api를 잘 섞으면 곡선은 아닐지라도 조금은 섬세한 도형을 그릴 수 있을 것 같았다.

내가 간략하게 만든 코드와 선 거리 구하기로 구현한 코드를 합쳐야했는데 비슷한 메소드를 쓴 듯 하면서도 달라서 머리가 터지는 줄 알았고 2-3번 갈아엎었다😭

여러 이슈들이 있었지만 이번에 Zustand로 만든 상태를 가져와서 사용하는 과정에서 있었던 트러블 슈팅을 정리해보려한다.

팀프로젝트 트러블슈팅(무한루프, 데이터 undefined)

01. 무한루프 발생

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;

🚨 원인

setRouteDataWalkPath 컴포넌트 안에서 바로 호출하고 있기 때문이었다. setRouteData가 상태를 업데이트하면 컴포넌트가 다시 렌더링되고, 다시 setRouteData가 호출되어 무한 루프가 발생하는 상황이다.

이를 해결하려면, setRouteDatauseEffect 내부에서 호출하여 distancepaths가 변경될 때만 호출되도록 수정했다. 이렇게 하면 상태가 변할 때만 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가 변경될 때만 실행되도록 의존성 배열 추가


02. 데이터가 undefined로 저장되는 상황

콘솔에 거리 데이터는 잘 담기는데 폼 작성 후 제출하면 db.json에 posiiton을 제외한 다른 데이터는 저장이 되지 않는게 큰 문제였다..!!!🤯

SaveUserRouteInfo 컴포넌트에서 json-server에 저장할 때, routeFormData는 잘 전달되지 않아서 거리만 저장되는 상황이라고 생각했다.

userRouteStore 주스탠드 코드

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;

WalkPath 컴포넌트 - 지도 그린 후 position 값과 예상 시간 거리 계산

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;

SaveUserRouteInfo 컴포넌트 - form에 입력한 데이터를 관리

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;

👩🏻‍💻 1차 수정한 코드

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,
    })
  );

✅ db.json에 제대로 담기는지 확인!

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도 써보면서 경험치는 쌓은 것 같은데 아직도 헷갈리는 부분이 많고 이게 맞나? 쓰면서도 제대로 쓰고있는게 맞나? 생각이 들었다..ㅋㅋㅋ 우선 발표가 끝나면 코드들을 다시 보면서 긴가민가했던 부분들을 튜터님들께 맞는지 확인해봐야할 것 같다.

profile
왕쪼랩 탈출 목표자의 코딩 공부기록

0개의 댓글

관련 채용 정보