Can't perform a React state update on an unmounted component
노마드코더 클론코딩 이벤트에 참여하고 싶어서 리액트 훅 강의 듣고서 파이어베이스를 기반으로 한 트위터 클론코딩 강의를 듣기 시작했다. 이렇게 저렇게 버그가 있진 않은가 테스트를 해보다가 마주한 문구.
Warning: Can't perform a React state update on an unmounted 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.
문제가 어디에서부터 시작 되었는지 몰라서 문장 자체를 넣어서 구글링을 해봤더니 내 경우에는 사용을 마친 컴포넌트가 렌더링되지 않도록 멈추는 코드를 작성해야 했던 것이다. 처음에는 상관이 없는 항목을 return을 해보았는데 어쩔 때는 경고 문구가 뜨지 않고, 또 어떤 때는 경고 문구가 뜨는 것이라서 계속 생성되는 머릿 속의 물음표. 구글링을 해서 얻은 자료로는 나와 비슷한 상황이라는 건 알겠는데 아니라서 뭘 어떻게 고쳐야할지 모르겠고, 혹시나 하는 마음에 다른 수강생 분들의 코멘트를 봤더니 역시나 나보다 먼저 에러를 경험하고 해결하신 분의 코멘트가 있었다.
useEffect(() => {
const tweets = query(
collection(dbService, 'tweets'),
orderBy('createdAt', 'desc')
);
onSnapshot(tweets, snapshot => {
const tweetArr = snapshot.docs.map(doc => ({
id: doc.id,
...doc.data()
}));
setTweetList(tweetArr);
});
}, []);
처음에 썼던 코드는 이랬는데 트윗리스트가 하나도 없는 상태에서 트윗을 작성하고 submit을 하면, 또 로그아웃을 했을 시에 경고가 떴다. 어떤 항목을 고쳐야할 지 감이 안 와서 Firebase 문서에다가 'unsubsribe'라고 검색을 해봤다. 알고보니 처음부터 이렇게 하라고 문서에는 친절하게 적혀 있었는데 영어라 정확하게 이해하지 못하고 대충 넘겨서 그 방법을 몰랐던 것.
useEffect(() => {
const tweets = query(
collection(dbService, 'tweets'),
orderBy('createdAt', 'desc')
);
const unsubscribe = onSnapshot(tweets, snapshot => {
const tweetArr = snapshot.docs.map(doc => ({
id: doc.id,
...doc.data()
}));
setTweetList(tweetArr);
});
return () => unsubscribe();
}, []);
위의 코드를 이 코드처럼 Firebase에서 가져온 onSnapshot이라는 컴포넌트에 변수를 할당하고 이를 다시 함수로 리턴을 하면 위와 같은 문구는 더 이상 뜨지 않는다. 이렇게 해결책을 찾긴 했으나 이 근본적인 문제가 어디에서부터 시작되어서 왜 이런 방법을 쓰면 경고가 뜨지 않는 지 잘 모르겠다. useEffect 훅을 쓸 때 Lifecycle componentDidMount, componentWillUnmount까지 포함해서 쓰는 방법이라고 했는데 그래서 쓰지 않는 컴포넌트는 렌더링 되지 않도록 해야하는 건가.
근본적인 물음에 대한 답을 찾고자 리액트의 공식 문서를 찾아보았더니,
정리(clean-up)를 이용하는 Effects
위에서 정리(clean-up)가 필요하지 않은 side effect를 보았지만, 정리(clean-up)가 필요한 effect도 있습니다. 외부 데이터에 구독(subscription)을 설정해야 하는 경우를 생각해보겠습니다. 이런 경우에 메모리 누수가 발생하지 않도록 정리(clean-up)하는 것은 매우 중요합니다.
effect에서 함수를 반환하는 이유는 무엇일까요? 이는 effect를 위한 추가적인 정리(clean-up) 메커니즘입니다. 모든 effect는 정리를 위한 함수를 반환할 수 있습니다. 이 점이 구독(subscription)의 추가와 제거를 위한 로직을 가까이 묶어둘 수 있게 합니다. 구독(subscription)의 추가와 제거가 모두 하나의 effect를 구성하는 것입니다. / Using the Effect Hook
외부 데이터에 구독을 설정해야 하는 경우, 메모리의 누수가 발생하지 않도록 정리하는 것이 매우 중요하다고 되어 있다. 정리를 하지 않아도 되는 경우가 있지만 그렇지 않은 경우에는 정리를 해줘야 하는 것.
유튜브 드림코딩에서 엘리님이 공식문서로 공부를 하는 것이 좋다고 했는데 진짜 다시 한 번 꼼꼼하게 문서를 읽어봐야겠다.