Warning: Can't perform a React state update on an unmounted component. <memory leak> 오류

ssummer·2023년 9월 12일

오류노트

목록 보기
3/5
post-thumbnail

중고마켓 사이드바에 최근 본 상품을 보여주는 컴포넌트를 만들고 있었다. 작성했던 코드를 간략히 해보자면 아래와 같다.

// viewItemList.index.tsx
import { useRecoilState } from "recoil";
import { todayViewState } from "../../commons/stores";
import { ViewItem } from "./ViewItem.index";

export const ViewItemList = (): JSX.Element => {
  const [todayView] = useRecoilState(todayViewState);

  return (
      <S.Container>
        <S.Title>최근 본 상품</S.Title>
        {todayView.map((id) => (
          <ViewItem key={uuidv4()} id={id} />
        ))}
      </S.Container>
  );
};
// viewItem.index.tsx
interface IViewItemProps {
  id: string;
}

export const ViewItem = (props: IViewItemProps): JSX.Element => {
  const {data} = useQueryFetchUseditem({useditemId: props.id});
  return (
    <S.Item>
		<S.ItemTitle>{data?.fetchUseditem.title}</S.ItemTitle>
	    ...
    </S.Item>
  );
};

useRouter로 쿼리 아이디인 상품아이디를 recoil에 배열구조로 여러개 저장해 각각의 ViewItem 컴포넌트에서 useQuery로 상품 정보를 불러와 보여주는 로직이었다. 그랬더니 아래와 같은 오류가 나타났다.

Warning: Can't perform a React state update on an umounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.

언마운트 된 컴포넌트에서 state를 업데이트 할 수 없다. 아무 일도 수행하지 않지만 메모리 누수일 수 있다. 고치려면 useEffect cleanup 함수에서 모든 구독과 비동기 태스크를 취소해라~

비동기적으로 실행되는 실행 컨텍스트가 완료되지 않은 상태에서 해당 컴포넌트가 unmount 되었고 그 후 state를 업데이트 하지 못할 때 생기는 오류다.

📌 참고했던 블로그 - https://norwayy.tistory.com/370

리액트로 개발하는 사람은 한번씩은 꼭 보게 된다는 오류답게 이 오류가 뜨는 상황도 정말 다양했다. 구글링하면서 본 코드는 모두 setState를 쓰고 있었고, 많은 경우가 페이지 이동이나 바인딩 된 이벤트에 따른 렌더링 등이었다.

내 경우에는, useQuery를 이용해 비동기로 데이터를 받아오고, 메인 페이지에서 조건 없이 처음부터 렌더링 되는 방식이라 내가 찾아본 것들과는 거리가 좀 있었다.

물론 내가 아직 모르는 확실한 답안이 존재하겠지만 머리 싸매고 구글링하고 이것저것 시도해봐도 뚜렷한 돌파구가 보이지 않았다. 배웠던 이벤트 루프를 되새기며 이 상황에 적용해 해석해보려했지만 아직 많이 어려웠다.. 😭



아무래도 컴포넌트 안의 컴포넌트에서 쿼리로 데이터 페칭을 하는 것이 문제인 것 같았다. 맨땅에 헤딩을 여러번 하다가 idrecoil에 저장했던 커스텀 훅에서 데이터를 조회하고 데이터까지 같이 recoil에 저장하는 방식으로 변경했다. 메모리 누수 오류는 사라졌다.

// useTodayView.tsx
import { useRecoilState } from "recoil";
import { todayViewState } from "../../stores";
import { useQueryFetchUseditem } from "../queries/useQueryFetchUseditem";

export const useTodayView = (): void => {
  const router = useRouter();
  const [todayView, setTodayView] = useRecoilState(todayViewState);

  const { data } = useQueryFetchUseditem({ useditemId: 현재 페이지 상품 id });
  const viewItem = [
    {
      // data
    },
  ];
  useEffect(() => {
    const filteredPrevTodayView = todayView.filter(
      (item) => item.id !== curItemId,
    );
    let tempList = [];
    if (data !== undefined) {
      if (filteredPrevTodayView.length === 0) tempList = [...viewItem];
      else tempList = [...viewItem, ...filteredPrevTodayView];
    }

    setTodayView(tempList.slice(0, 3));
  }, [data]);
};

어떤게 더 효율적이고 장기적으로 봤을 때 괜찮은 방식인지는 아직 잘 모르겠다. 갈 길이 멀다......


추후 업데이트 되는 리액트 버전에서는 이 오류를 띄우지 않도록 한다는 내용을 봤는데, 그럼 이게 그렇게 크리티컬한 오류는 아니란 말인 것 같기도 하고..?

0개의 댓글