Grouping List Rendering Logic

Myung Jin Kim·2023년 8월 9일
1

Feedback

목록 보기
1/1

API 에서 주는 아이템들을 그루핑해서 화면에 보여줘야 하는 케이스가 생겼는데, 이를 구현한 로직을 리펙토링 하면서 내가 잘못 알고 있는 지식을 정정하고 비슷한 상황이 다시 찾아왔을 때 잘못된 코드를 반복해서 작성하지 않고자 포스팅을 한다.


초안

랜덤하게 섞여있는 아이템들을 그루핑하기 위해서는 먼저 루프를 돌면서, 각각의 아이템들을 그룹을 짓고자 하는 기준을 가지고 배열에 담아줘야 한다. 형태는 key, value 여야 할 것이고 이때 value 는 배열이 되어야 할 것이다.

Javascript 에서 이를 지원하는 Data Type 으로는 Object 와 Map 이 떠올랐는데, 자주 쓰던 Object 보다는 Map 을 통해서 렌더링 로직을 구현하고 싶었고 Map 에서 지원하는 메소드를 이용하면 좀 더 쉽게 렌더링 로직을 구현할 수 있을 거라 생각했다.

그렇게 해서 작성한 코드는 다음과 같다.

const OrderList = ({ orderList }: { orderList: DummyDataItemType[] }) => {
  const groupListRef = useRef(new Map());

  useEffect(() => {
    orderList.forEach((dummyData: DummyDataItemType) => {
      const groupedList = groupListRef.current.get(dummyData.group);
      if (!groupedList) {
        groupListRef.current.set(dummyData.group, [dummyData]);
      } else {
        groupedList.push(dummyData);
      }
    });
  }, []);

  let isDone = false;
  const renderOrderList = [];
  const groupListIter = groupListRef.current.entries();
  let key = 0;
  while (!isDone) {
    const nextNode = groupListIter.next();
    isDone = nextNode.done ?? true;
    if (!isDone) {
      key++;
      renderOrderList.push(
        <div key={key}>
          <p>{nextNode.value[0]}</p>
          {nextNode.value[1].map((orderDetailItem: DummyDataItemType) => {
            return (
              <div>
                <p>{orderDetailItem.title}</p>
                <p>{orderDetailItem.label}</p>
              </div>
            );
          })}
        </div>
      );
    }
  }
  return renderOrderList;
};

해당 코드에는 다음의 문제점들이 있었다.

  • 해당 페이지로 처음 라우팅 되었을 때 ListItem 들이 렌더링 되지 않았다.
  • 리렌더링이 발생할 때마다 ListItem 들이 렌더링 횟수만큼 중복되면서 렌더링이 된다.
  • 페이지단에서 데이터 처리 로직 및 렌더링 로직이 분리되어서 들어가다 보니 코드가 복잡하다.

1번과 2번 문제는 useRef Hook 으로 렌더링 되야 하는 데이터를 처리했기 때문이다. useRef 는 re-rendering 이 발생했을 때 트리거가 되지 않는 Hook 이다 때문에 useEffect 에서 useRef 의 데이터 변경이 생김에도 불구하고 이는 리렌더링이 되지 않을 거고 때문에 그룹핑된 리스트는 렌더링되지 않는다.

React Document 에서 useState 를 쓰는 경우는 사용자의 인터렉션이 있어서 이벤트 핸들링에 의해서 컴포넌트 내부에서 관리해야 하는 상태가 생기는 경우에만 쓰고 그 외는 꼭 필요한지 고려해보라고 본 거 같아서 단순 리스트를 화면에 보여줘야 하는 이번 경우 useState 는 안써야겠다고 생각했기에 이러한 코드를 작성하게 되었다.

3번의 경우, 애초에 데이터 가공을 페이지단에서 할 게 아니라 데이터를 Fetching 하고 나서 Global State 로 Jotai 를 쓰고 있으니 Derived Readable Atom 에서 가공을 하고 이를 단순 디스플레잉하는 방식으로 가져갔다면 애초에 1번과 2번의 문제도 발생하지 않았을 것이다.

우선 1번과 2번 문제를 해결해본다면 다음과 같이 코드를 작성할 수 있다.

const OrderList = ({ orderList }: { orderList: DummyDataItemType[] }) => {
  const [orderItems, setOrderItems] = useState<OrderItemType[]>([]);
  useEffect(() => {
    const groupListMap = new Map();
    orderList.forEach((dummyData: DummyDataItemType) => {
      const groupedList = groupListMap.get(dummyData.group);
      if (!groupedList) {
        groupListMap.set(dummyData.group, [dummyData]);
      } else {
        groupedList.push(dummyData);
      }
    });

    let isDone = false;
    const tempOrderItems = [];
    const groupListIter = groupListMap.entries();
    let key = 0;
    while (!isDone) {
      const nextNode = groupListIter.next();
      isDone = nextNode.done ?? true;
      if (!isDone) {
        const [groupTitle, groupItems] = nextNode.value;
        tempOrderItems.push({ groupTitle, groupItems });
      }
    }
    setOrderItems(tempOrderItems);
  }, [orderList]);

  return orderItems.map(orderItem => {
    return (
      <div>
        <h3>{orderItem.groupTitle}</h3>
        <div>
          {orderItem.groupItems.map(groupItem => {
            return (
              <div>
                <p>{groupItem.title}</p>
                <p>{groupItem.label}</p>
              </div>
            );
          })}
        </div>
      </div>
    );
  });
};

정리
어설프게 알고 있던 지식들이 모이고 모여서 이번과 같은 코드를 짜게 되었는데 자주 쓰는 React Hooks 를 다시 확인해보고 블로그에 이를 포스팅 해나가겠다.

profile
개발을 취미로 하고 싶은 개발자를 목표로 살고 있습니다

1개의 댓글

comment-user-thumbnail
2023년 8월 9일

정보에 감사드립니다.

답글 달기