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;
};
해당 코드에는 다음의 문제점들이 있었다.
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 를 다시 확인해보고 블로그에 이를 포스팅 해나가겠다.
정보에 감사드립니다.