본문 바로가기

프로그래밍

[React]useEffect가 2번 호출되는 이유(feat. StrictMode)

문제 확인

React 18 버전을 사용하여 애플리케이션 개발을 진행하던 도중 별다른 코드를 작성하지 않았는데 useEffect() 안의 내용이 두 번 출력되는 경우가 발생하였습니다. 이 현상은 개발 시에만 등장하고 실제로 배포할 때에는 현상이 나오지 않아 초반에 꽤 애를 먹었던 오류입니다.

function App() {
  useEffect(() => {
    console.log("mount");

    return () => console.log("unmount");
  }, []);

  return (
    <div className="App">
    </div>
  );
}

useEffect()가 한번이 불려야하는데 '마운트->언마운트->마운트'로 2번이 불리는 걸 확인 할 수 있습니다.

왜 이런 문제가 생기는가?

이러한 문제는 이전 React 버전에서는 등장하지 않고, React 18 이상을 설치한 프로젝트부터 등장합니다. 이것은 React 18 버전부터 추가된 StrictMode 때문입니다. create-react-app 명령어를 사용하여 React 프로젝트를 구성하였다면 본인이 아무것도 하지 않아도 React.StrictMode가 코드에 추가가 되어있어 StrictMode의 영향으로 이런 이상한 메시지가 뜨게 되는 것입니다.

React.StrictMode

프로젝트의 index.js를 확인해보면 아래 코드처럼 App 컴포넌트를 React.StrictMode 태그가 감싸고 있는 것을 알 수 있습니다.

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

React.StrictMode는 React.Fragment(JSX에서 2개 이상의 형제 노드를 묶어주는 태그. <></>)와 동일하게 실제로 웹 페이지 상으로는 전혀 출력되지 않습니다. 또한, 직접적으로 프로젝트에 영향을 주는 기능도 아닙니다.

React.StrictMode는 개발자들에게 해당 코드가 잠재적으로 문제가 있을 수 있음을 알리기 위해서 '개발 모드에서' 사용되는 기능입니다.

때문에 React.StrictMode는 개발 환경에서만 동작하며, 실제 배포 환경에서는 동작하지 않습니다.

 

https://en.reactjs.org/docs/strict-mode.html

 

Strict Mode – React

A JavaScript library for building user interfaces

reactjs.org

React.StrictMode에 대한 자세한 글은 차후에 정리해서 업로드할 예정입니다.

다시 문제로 돌아와서 useEffect가 2번 불리게 된 이유는 위의 공식 문서에서 마지막으로 작성한 부분때문입니다.

Ensuring reusable state(재사용 가능한 상태를 보장하기 위해서)

그 아래의 본문을 보면 리액트가 미래에 어떻게 변화하고 싶은지에 대한 내용이 적혀있습니다. 짧게 정리하자면, 사용자가 웹 페이지를 앞뒤로 이동할 때, 이전 페이지로 돌아오면 바로 화면에 보여주기 위해 이전과 같은 상태를 사용해서 트리를 다시 마운트 할 필요가 있다는 것이 필요하다는 것입니다.

In the future, we’d like to add a feature that allows React to add and remove sections of the UI while preserving state. For example, when a user tabs away from a screen and back, React should be able to immediately show the previous screen. To do this, React would unmount and remount trees using the same component state as before.

페이지를 오고 갈 때마다 이전 상태를 유지한 상태로 리마운트를 한다면 여러 번 리마운트될 때마다 화면이 달라지지 않고, 같은 화면을 표시할 수 있어야 합니다. 이런 조건을 체크하기 위해서 React 18버전부터는 여러번 마운트 되었을 때 문제가 될 수 있는 부분이 있는지 확인할 수 있도록 StrictMode를 제공하는 것입니다. 이런 식으로 두 번 마운트가 되는 동안 영향을 받는 구간은 아래와 같습니다.

  • componentDidMount
  • componentWillUnmount
  • useEffect
  • useLayoutEffect
  • useInsertionEffect

StrictMode의 등장으로 당장 애플리케이션에 문제가 생긴 것이 아니라, 이런 식으로 변경될 건데 지금 이대로 놔두면 나중에 문제가 있을 수 있으니 확인하라는 경고 차원으로 보시면 될 것 같습니다. 실제로 유저에게 제공되는 환경에서는 전혀 영향을 미치지 않으니 그리 급하게 수정할 필요도 없습니다.

 

해결법

  1. 클린업 함수를 작성하여 상태를 확실하게 초기화하기
    간단한 콘솔을 찍거나, 상태를 특정 값으로 지정하는 것은 두 번 마운트 되었다고 프로젝트에 영향은 주지 않습니다. 다만, API 호출이나 값이 변하거나 특정 동작을 수행하는 라이브러리의 사용은 마운트가 될 때마다 값이 변경되어 예상했던 결과가 아닌 다른 결과를 호출하게 될 수도 있습니다.(아니면 두 번의 요청에 대한 결과가 모두 화면에 표시될 수도 있습니다.)
    때문에 이런 영향으로부터 벗어나기 위해서 첫 마운트에 대한 언마운트 과정이 진행될 때, 이전에 설정되었던 값을 클린업 함수에서 제대로 초기화가 되도록 설정해주어야 합니다. 특히, useEffect 내부에서 API 요청을 한 경우 중복 요청으로 인해 문제가 발생할 수도 있기 때문에 반드시 이전 요청에 대한 처리를 해주어야 하는데 대표적으로 AbortController()를 사용하는 방법이 있습니다.(혹은 API마다 번호를 매겨서 가장 최신의 번호의 요청 답변만 처리가 되도록 할 수도 있습니다.
      useEffect(() => {
        const controller = new AbortController();
        fetch("/people", {
          signal: controller.signal,
        })
          .then((res) => console.log(res.json()))
          .then(setPeople);
        return () => controller.abort(); //API 진행 중일 경우 요청 취소
      }, []);
  2. React.StrictMode 제거
    두 번 마운트 되는 것이 애플리케이션을 운영하는데 큰 문제가 발생하지 않는다면 React.StrictMode를 제거하는 게 제일 간단한 방법입니다. 지금 제공되는 StrictMode는 치명적인 오류를 만들어내는 것이 아닌 잠재적인 문제를 찾을 수 있도록 도움을 주는 기능에 지나지 않기 때문에 필요가 없다고 판단되면 제거 후 사용해도 무방합니다.
    const root = ReactDOM.createRoot(document.getElementById('root'));
    root.render(
        <App />
    );