
중고마켓 사이드바에 최근 본 상품을 보여주는 컴포넌트를 만들고 있었다. 작성했던 코드를 간략히 해보자면 아래와 같다.
// 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를 이용해 비동기로 데이터를 받아오고, 메인 페이지에서 조건 없이 처음부터 렌더링 되는 방식이라 내가 찾아본 것들과는 거리가 좀 있었다.
물론 내가 아직 모르는 확실한 답안이 존재하겠지만 머리 싸매고 구글링하고 이것저것 시도해봐도 뚜렷한 돌파구가 보이지 않았다. 배웠던 이벤트 루프를 되새기며 이 상황에 적용해 해석해보려했지만 아직 많이 어려웠다.. 😭
아무래도 컴포넌트 안의 컴포넌트에서 쿼리로 데이터 페칭을 하는 것이 문제인 것 같았다. 맨땅에 헤딩을 여러번 하다가 id만 recoil에 저장했던 커스텀 훅에서 데이터를 조회하고 데이터까지 같이 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]);
};
어떤게 더 효율적이고 장기적으로 봤을 때 괜찮은 방식인지는 아직 잘 모르겠다. 갈 길이 멀다......
추후 업데이트 되는 리액트 버전에서는 이 오류를 띄우지 않도록 한다는 내용을 봤는데, 그럼 이게 그렇게 크리티컬한 오류는 아니란 말인 것 같기도 하고..?