🤔 발견한 문제
나는 오늘 데브코스에서 리액트 예제를 공부하던 중 몇 가지 이상한 점을 발견했다.
1. 같은 코드를 작성했지만 에러가 출력됐다.
강사님과 같은 코드를 작성했는데 나는 에러가 출력됐다.
useHover.js
import { useCallback, useEffect, useRef, useState } from 'react';
const useHover = () => {
const [state, setState] = useState(false);
const ref = useRef(null);
const handleMouseOver = useCallback(() => setState(true), []);
const handleMouseOut = useCallback(() => setState(false), []);
useEffect(() => {
const element = ref.current;
if (element) {
element.addEventListener('mouseover', handleMouseOver);
element.addEventListener('mouseout', handleMouseOut);
return () => {
// 강의와 동일한 코드입니다 -> 에러
//element.removeEventListener('mouseover');
//element.removeEventListener('mouseout');
// 핸들러를 명시해주면 에러가 나지 않습니다.
element.removeEventListener('mouseover', handleMouseOver);
element.removeEventListener('mouseout', handleMouseOut);
};
}
}, [ref, handleMouseOut, handleMouseOver]);
return [ref, state];
};
export default useHover;
위와 같이 강사님의 코드는 removeEventListener에 두번째 인자로 핸들러를 명시해 주지 않았다. 하지만 에러가 나지 않았다. 강사님께 여쭤본 결과, removeEventListener의 두번째 인자는 반드시 핸들러를 명시해주어야 하기 때문에 사실 이 코드는 잘못된 코드였다.
하지만 왜 내 코드에서는 에러가 발생했고 강사님의 코드에서는 에러가 발생하지 않았을까?
2. console.log 가 두 번 출력된다.

두 번째로 발견한 점은 위와 같이 두번씩 콘솔이 찍힌다는 것이다. 하지만 강사님의 경우, 콘솔이 한 번만 찍혔다. 참고로 콘솔을 찍은 App.js 코드는 아래와 같다.
import Checkbox from './Checkbox';
import useToggle from './useToggle';
function App() {
const [on, toggle] = useToggle();
console.log(on);
return (
<div>
<Checkbox checked={on} onChange={toggle} />
</div>
);
}
export default App;
왜 나는 콘솔이 두 번 찍혔을까?
❗ React.strictmode
결과부터 말하자면 강사님은 React.strictmode가 아니었고, 나는 React.strictmode 였기 때문이다.
StrictMode는 애플리케이션 내의 잠재적인 문제를 알아내기 위한 도구입니다. Fragment 와 같이 UI를 렌더링하지 않으며, 자손들에 대한 부가적인 검사와 경고를 활성화합니다.
React 공식문서에서 볼 수 있는 Strict 모드에 대한 설명이다.
index.js
원인은 index.js 에서 React.strictmode 로 App을 감싸주었기 때문이다.
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
1. React.strictmode는 잠재적인 위험을 검사한다.
React.strictmode는 애플리케이션 내의 잠재적인 위험을 검사한다. 첫 번째 문제에서 강사님의 코드는 useEffect의 return (clean-up) 라이프사이클이 검사되지 않았기 때문에 return은 아예 무시됐다. 따라서 잘못된 코드임에도 실행단계에서 검출되지 못했기 때문에 에러가 발생하지 않았다.
하지만 나는 Strictmode였기 때문에 리액트는 라이프사이클을 모두 검사했다. 따라서 clean-up 을 실행시켜보니 잘못된 코드였기 때문에 에러를 발생시킨 것이다.
2. console.log가 두 번 찍히는 것은?
스택오버플로우에서는 React.strictmode가 원인이라고 한다. 아마도 Strictmode에서는 라이프사이클을 검사하는 과정에서 코드를 2번 실행시키는 것 같다. 공식문서를 다시 찾아보았다.
Strict 모드가 자동으로 부작용을 찾아주는 것은 불가능합니다. 하지만, 조금 더 예측할 수 있게끔 만들어서 문제가 되는 부분을 발견할 수 있게 도와줍니다. 이는 아래의 함수를 의도적으로 이중으로 호출하여 찾을 수 있습니다.
- 클래스 컴포넌트의 constructor, render 그리고 shouldComponentUpdate 메서드
- 클래스 컴포넌트의 getDerivedStateFromProps static 메서드
- 함수 컴포넌트 바디
- State updater 함수 (setState의 첫 번째 인자)
- useState, useMemo 그리고 useReducer에 전달되는 함수
리액트 공식문서를 살펴보니, React.strictmode에서는 의도적으로 함수를 이중으로 호출한다고 나와있다.
물론 Strictmode는 개발모드상에서만 적용이 되고, 프로덕션 모드에서는 무시된다.
Strictmode가 무엇인지 알고 있다면 굳이 사용하지 않을 이유는 없을 것 같다. 개발 중에서 나도 모르게 일어날 수 있는 에러를 잡아낼 수 있으니 계속해서 사용하면 좋을 것 같다.
📎 참고자료
리액트 공식문서 - Strict 모드
https://ko.reactjs.org/docs/strict-mode.html
Strict 모드 – React
A JavaScript library for building user interfaces
ko.reactjs.org