Next.js

React 상태 관리 문제 해결 과정

해보구 2025. 3. 7. 19:39
반응형

Next.js로 주문 내역 페이지를 만들고 있었다. 왼쪽에 주문 목록을 띄우고, 클릭하면 중간 섹션(Middle Section)에 상세 정보가 나와야 하는 구조였다. 근데 주문을 클릭해도 중간 섹션이 빈 채로 남아 있었다. 콘솔을 열어보니 이런 로그가 찍혀 있었다:
page.tsx:189 Selected order in useEffect: undefined
page.tsx:227 Order not found for selectedOrderId: 164

API 호출은 잘 됐는데, 왜 데이터가 안 보이는 걸까? 하루를 이거 풀어보느라 씨름했다.

 

원인 파악

코드를 뜯어보니 문제는 상태 관리에 있었다.

  • 상태 두 개로 나뉘어 있었다:
    • sortedGroups: /api/orders/daily에서 받은 주문 데이터를 날짜별로 정리한 상태.
    • dailyOrders: Zustand 스토어에서 관리하는 별도의 주문 데이터.
    • 두 상태가 서로 동기화되지 않고 따로 놀았다.
  • 비동기 타이밍 문제:
    • handleOrderClick에서 주문을 클릭하면 setSelectedOrderselectedOrderIdselectedDate를 설정하고, dailyOrders가 없으면 API를 호출해서 채웠다:
const handleOrderClick = async (order) => {
  const date = new Date(order.orderedAt).toISOString().split("T")[0];
  setSelectedOrder(order.orderId, date);
  if (!dailyOrders[date]) {
    const response = await axiosInstance.get(`/api/orders/daily`, { params: { storeId, date } });
    setDailyOrders(date, response.data);
  }
};

 

  • 근데 setSelectedOrder가 실행되자마자 useEffect가 돌면서 dailyOrders[selectedDate]를 확인했는데, 이때 setDailyOrders가 아직 완료되지 않아 데이터가 비어 있었다:
useEffect(() => {
  if (selectedOrderId && selectedDate && dailyOrders[selectedDate]) {
    const order = dailyOrders[selectedDate].find((o) => o.orderId === selectedOrderId);
    // order가 undefined
  }
}, [selectedOrderId, selectedDate, dailyOrders]);

 

데이터 소스 불일치:
  • sortedGroups에는 API 데이터가 잘 들어갔는데(예: [{date: '2025-03-07', orders: Array(2)}]), 중간 섹션은 dailyOrders만 쳐다봤다. 이중 관리 때문에 혼란이 생긴 거였다.

 

 

하루 고민 끝에 접근을 바꿨다. 상태를 단순화하고 타이밍 문제를 잡아야겠다고 결심했다.

 

  • 상태 통합:
    • dailyOrders를 없애고 sortedGroups만 남겼다. 어차피 sortedGroups에 모든 주문 데이터가 있었으니 중복 상태는 필요 없었다.
    • Zustand에서 dailyOrderssetDailyOrders를 지우고, sortedGroups를 단일 소스로 삼았다.
  • useEffect 수정:
  • dailyOrders 대신 sortedGroups에서 주문을 찾도록 고쳤다:
useEffect(() => {
  if (selectedOrderId && selectedDate) {
    const group = sortedGroups.find((g) => g.date === selectedDate);
    const order = group?.orders.find((o) => o.orderId === selectedOrderId);
    if (order) {
      setPlaceName(order.placeName || "Unknown");
      // receipt 불러오는 로직
    } else {
      setPlaceName("");
      setReceipt(null);
    }
  }
}, [selectedOrderId, selectedDate, sortedGroups]);

 

의존성 배열에 sortedGroups를 추가해서 데이터가 업데이트될 때마다 반응하도록 했다.

 

 

  • handleOrderClick 개선:
    • 클릭 시 sortedGroups에 데이터가 없으면 API 호출로 추가했다:
const handleOrderClick = async (order) => {
  const date = new Date(order.orderedAt).toISOString().split("T")[0];
  setSelectedOrder(order.orderId, date);
  const groupExists = sortedGroups.some((g) => g.date === date);
  if (!groupExists) {
    const response = await axiosInstance.get(`/api/orders/daily`, { params: { storeId, date } });
    setSortedGroups((prev) => [
      ...prev,
      { date, orders: response.data },
    ].sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()));
  }
};

 

 
 
렌더링 로직 통합:
  • selectedOrdersortedGroups에서 바로 가져오게 했다:
const selectedOrder =
  selectedDate && sortedGroups.length > 0
    ? sortedGroups
        .find((g) => g.date === selectedDate)
        ?.orders.find((o) => o.orderId === selectedOrderId) || null
    : null;

 

 

 

수정하고 나니 로그가 달라졌다:

page.tsx:145 API 응답: /api/orders/daily {status: 200, data: Array(2)}
page.tsx:167 sortedGroups 설정 완료: [{date: '2025-03-07', orders: Array(2)}]
page.tsx:189 Selected order in useEffect: {orderId: 164, price: 90000, ...}

 

 

  • sortedGroups에 데이터가 잘 들어갔고, useEffect에서 주문을 정확히 찾았다.
  • Middle Section에 placeNamereceipt 정보가 드디어 표시됐다. 하루 만에 해결한 셈이다.

 

회고

처음엔 API 응답이 잘못됐나, 데이터 구조가 문제인가 고민했다. 근데 로그를 하나씩 보니 상태 관리 실수가 눈에 들어왔다. dailyOrderssortedGroups를 왜 따로 뒀는지 모르겠다 싶었다. 이번에 코드 단순화하면서 머리도 정리된 느낌이다. 다음엔 상태 설계할 때 좀 더 신경 써야겠다고 다짐했다. 코드도 깔끔해지고 배운 것도 많은 경험이었다.

 

반응형