본문 바로가기
  • Seizure But Okay Developer
FrontEnd/React 관련자료

에러노트 - Can't perform a React state update on an unmounted component

by Sky_Developer 2021. 3. 12.

배경

ant-design 을 사용해 운영중인 프로젝트 에서 작업을 하던 도중, 아래와 같은 오류가 발생했다.

 

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.

 

경고, 언마운티드된 컴포넌트에 대해선 상태를 업데이트할 수 없다.
이 작업은 수행되지 않지만, 메모리 누수가 발생하게 된다.
고치려면, 모든 subscription과 비동기 작업을 useEffect 뒷정리 함수를 사용해서 해결해라.

 

의도치 않은 에러가 발생해서 당황했지만 검색하며 여러 도움되는 자료들을 많이 찾으면서 해결할 수 있었다

해결과정

(제 코드는 ant-design 프로젝트에 기반하여 짜여졌습니다. 그래서 ant-design 컴포넌트 사용에 관한 얘기가 다소 나오는데 앞뒤 맥락이 없게 느껴지더라도 양해 구합니다)
문제가 발생한 원인에 대해서 생각해봤다. 왜 언마운티드된 컴포넌트의 상태를 업데이트 한 걸까?
내 코드는 대충 아래와 같다

 

const columns = Config;
const Example = () => {
    const [componentUpdate, setComponentUpdate] = useState(false);
    ...
    useEffect(() => {
        if (columns.tableFields.every(column => column.dataIndex !== "OO")) {
          columns.tableFields.unshift({
          // 상태를 업데이트하는 부분
            ...
              render: (value, record) => <button onClick={() => setComponentUpdate(...)}>Show</button>
              ...
          })
        }
    }, []);

    return (
    <>
        <Table columns={columns} ... />
        <Drawer visible={componentUpdate} >
            blah blah blah
        </Drawer>
    </>
}

코드 설명

columns를 every 메서드로 검사한 후 unshift 하는 이유는 새로운 컬럼을 컴포넌트가 처음 마운트 됬을 때에만 추가하기 위해서다. (이부분은 이해 안되셔도 넘어가시면 됩니다 ㅠ) 그리하여 다른 파일에서 import 해온 다른 컬럼들과 합쳐진다.

 

그런데, useEffect 에서 두번째 배열 인자를 빈 배열 [] 로 두면 어떻게 동작할까?


컴포넌트가 처음 마운트 됬을 때만 useEffect 안의 내용들이 실행된다. 그럼 컴포넌트가 처음 마운트 되는 시기는 실제로 언제일까?
컴포넌트가 구현된 페이지를 로딩했을 때이다. 다른 페이지로 이동했다가 원 페이지로 돌아오면 페이지가 로딩되면서 컴포넌트도 마운트 된다.


(새벽에 쓰는 글이라..영 필력이 ㅠ)

 

다른 페이지로 이동할 때 Drawer 컴포넌트는 언마운티드 될 것이고, 다시 페이지로 돌아와서 Drawer 컴포넌트를 띄우려고 하면 위에서 설명했던 오류문구가 뜬다. Drawer 컴포넌트는 언마운티드 된 상태고, 언마운티드 된 컴포넌트 상태를 업데이트 하려다보니 오류가 발생한다.

해결 과정

그래서 구글링등을 통해 다른 글들을 많이 검색해봤다. 대부분 지역변수로 unmounted 또는 useRef 를 사용해 변수를 선언하고 false일땐 상태업데이트를 하다가 뒷정리 함수로 값을 true로 변경하며 상태업데이트를 막으며 해결을 한다. 근데 나는 상태 업데이트를 해줘야지만 Drawer 컴포넌트가 뜨면서 원하는대로 동작을 하는 구조다. 좀 더 응용해서 생각 할 필요가 있었다.

 

every 메소드로 검사하는 코드를 다시 살펴보니, 저 코드에서 columns는 처음 마운트 됬을 때 컬럼이 하나 추가 되고 이후 페이지를 다시 접근했을 땐 추가가 되지 않는다. 그러니까 맨 처음 페이지를 로딩했을 때의 코드가 계속 유지되고 있는 것이다.


언마운티드 된 컴포넌트의 상태를 업데이트 하는게 안된다면, 매번 새롭게 컴포넌트를 만들어주면 어떨까? 라는 생각을 하게 됬다. 이는 내가 컴포넌트가 마운티드 될때마다 내용을 새롭게 업데이트 해주지 못했다는 것도 의미한다.


unshift 한 컬럼값을 js return () => { columns.tableFields.shift(); } 같이 뒷 정리 함수로 매번 shift 시켜줘봤다.
그럼 페이지를 로딩할 때마다 컬럼이 새롭게 추가가 될 것이다. (새롭게라고 표현한 것은 다른 페이지로 이동할 때 즉 언마운티드될 때 컬럼은 없어질 것이기 때문)

 

그럼 굳이 매번 every 로 검사할 필요가 없어지므로 지워줬다.

결과적으로 매번 페이지를 로딩할 때마다 새롭게 생성되는 건 onClick에 등록된 핸들러 함수다. 그럼 이 핸들러 함수를 매번 생성해줘야만 오류가 발생하지 않는다는 것이다.


columns 는 지역변수로 컴포넌트가 처음 마운트 됬을때 생성이 된다. 근데 이 columns 를 맨 처음 한번만 업데이트 해줬고 그에 따라 onClick 에 등록된 핸들러 함수도 새롭게 등록이 안되면서 언마운티드된 컴포넌트를 계속 쥐고 있던 것이라고 생각한다. columns 값을 매번 뒷정리함수로 지워주고 새롭게 등록을 하면서 바로 해결이 된 것을 보면 저런 뒷 배경이 있지 않을까라고 유추를 했다.

혹시 설명이 부족하거나 틀린 부분이 있다면 댓글로 피드백을 주면 감사하겠습니다.

참고자료

https://velog.io/@ohgoodkim/-%EC%97%90%EB%9F%AC%EB%85%B8%ED%8A%B8-Cant-perform-a-React-state-update-on-an-unmounted-component
https://stackoverflow.com/questions/58038008/how-to-stop-memory-leak-in-useeffect-hook-react

댓글