useState에서 상태 변경 시 바로 재렌더링 일어나지 않는 버그

화영화영
2 min read

🐞 문제상황

리스트 정렬 변경 시 변경된 리스트가 바로 적용되는 것이 아니라 변경 탭을 한번 더 클릭하면(modalVisible 상태가 변경되면) 그때 리스트가 변경된다

💡해결과정

  • 기존 코드

    정렬방법 탭을 클릭하면 모달이 열리면서 modalVisible라는 상태가 false/true로 변경된다. 그리고 열려있는 모달에서 정렬방법을 클릭하면 sort를 사용하여 pickList의 순서를 변경시키고 sortedList에 저장한다. 이전 리스트와 순서가 다를 경우에만 setPickList에 sortedList를 넣어 상태를 변경시키도록 했다.

      const [modalVisible, setModalVisible] = useState(false); // 모달보기
      const [order, setOrder] = useState<TOrder>('time'); // 정렬방법  
    
      useEffect(() => {
        if (pickList) {
          let sortedList;
          if (order === 'time') {
            // 최신순
            sortedList = pickList.sort(
              (a, b) => new Date(b.CREA_DT).getTime() - new Date(a.CREA_DT).getTime(),
            );
          } else if (order === 'recomment') {
            // 추천수 순
            sortedList = pickList.sort((a, b) => b.RECOM_CNT - a.RECOM_CNT);
          } else {
            // 댓글 순
            sortedList = pickList.sort((a, b) => b.COMMENT_CNT - a.COMMENT_CNT);
          }
    
          // 상태가 이전과 다를 때만 업데이트
          if (JSON.stringify(pickList) !== JSON.stringify(sortedList)) {
            setPickList([...sortedList]);
          }
        }
      }, [order, pickList]);
    

문제는 pickList에 sort를 사용하여 이미 pickList 상태를 변경시켰고, 상태가 이전과 다를 경우 set함수를 실행하는 것에 부합하지 않기 때문이다.

esLint에러로 의존성 배열에 pickList를 넣었더니 setPickList가 계속 일어나면서 무한 렌더링이 발생했다. 이 무한 렌더링을 막기 위해 pickList와 sortedList를 비교해서 다를 경우에만 setPickList를 수행하도록 했다. 하지만 저 조건문은 이미 변경된 pickList와 그 복사본인 sortedList를 비교하고 있었던 것… pickList는 변경되었지만 setPickList를 통해 변경된 것이 아니기 때문에 재렌더링이 일어나지 않았고, modalVisible를 한번 더 클릭하면서 상태변경이 일어날 때 리스트도 같이 재렌더링되었다.

  • 수정방향
  1. useState의 상태를 바로 변경해주는 sort를 원본 배열에 직접 사용하는 것은 좋지 않으므로, […data] 를 사용하여 깊은 복사를 해주고 sort를 해준다.

  2. 의존성배열에 pickList를 삭제하고, 기존 배열과 비교하는 조건문도 삭제한다.

useEffect(() => {
  if (pickList) {
    let sortedList;
    if (order === 'time') {
      sortedList = [...pickList].sort((a, b) => new Date(b.CREA_DT).getTime() - new Date(a.CREA_DT).getTime());
    } else if (order === 'recomment') {
      sortedList = [...pickList].sort((a, b) => b.RECOM_CNT - a.RECOM_CNT);
    } else {
      sortedList = [...pickList].sort((a, b) => b.COMMENT_CNT - a.COMMENT_CNT);
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
    setPickList(sortedList);
  }
}, [order]);

🎈결과

  • sort는 원본 배열을 변경시키므로 원본배열을 복사한 뒤 사용한다.

  • useEffect에서 useState의 상태를 의존성 배열에 넣고, 상태변화함수를 사용하면 무한렌더링이 일어난다.

  • useState의 상태는 데이터가 변경될때가 아닌 참조값이 변경되어야 렌더링이 트리거된다.

개발을 하면서 어이 없을 때는 알고 있는 개념인데 실제 코딩할 때는 그 개념을 자연스럽게 적용하여 코딩하는 것이 잘 안된다는 것이다. 이번 개념은 경험이 부족하다고 하기에 조금 민망할 정도의 기본 영역인 것 같다. 작업을 하면서 성공했다고 넘기는 것이 아니라 문제에 대해 파고들고 체화시키기 위해 시간을 가지는 것을 미루지 않아야겠다.

0
Subscribe to my newsletter

Read articles from 화영 directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

화영
화영