치춘짱베리굿나이스
Too many re-renders 오류 본문
😡
많은 프로젝트에서 나를 괴롭혔던 오류이다
툭하면 발생하는데 브라우저에는 컴포넌트가 하나도 렌더링되지 않고 흰 배경만 덩그러니 남아 있어서 더 킹받았던 오류
발생하는 이유도 제대로 몰랐는데 내 코드 리팩토링하면서 이유를 찾아버려 정리하기로 했따
왜 나를 괴롭히는가?
처음에는 각 태그에서 발생하는 이벤트의 기본 동작이 반복적으로 발생하는 줄 알고 e.preventDefault()
를 일일히 넣어주었고, 실제로도 해결되었다
하지만 e.preventDefault()
는 <a>
태그나 <input type=”checkbox” />
와 같이 특수한 태그에서 일어나는 기본 동작을 막아주는 용도이지, 애초에 button
이나 div
는 기본 동작이랄 게 없어서 저걸 쓰나 마나 별반 차이가 없었다
리팩토링을 하고 보니 문제점은 함수 그 자체에 있었다;;
...
const [testState, setTestState] = useState("");
...
const handleButtonClick = (v) => {
setTestState(v);
}
...
<button
className={testState}
onClick={handleButtonClick(v)}
>
리렌더링된다옹
</button>
위의 예시에서 handleButtonClick
은 인자로 v
를 받는다고 가정하자
handleButtonClick
은 클릭할 때마다testState
를 변화시킨다- 각 버튼들은
testState
에 따라 클래스명이 변화하므로testState
가 변할 때마다 리렌더링된다 - 이때 중괄호 안에
handleButtonClick(v)
를 바로 집어넣는다는 것은 실질적으로onClick
에 함수를 넣은 게 아니라handleButtonClick
함수에v
를 넣고 실행시킨 반환값을 이용하겠다는 의미가 된다 - 중괄호 안에 함수를 넣은 게 아니라 함수의 반환값을 넣었으므로,
onClick
에 함수가 걸린 게 아니라 변수가 걸린 게 된다 ⇒ 렌더링될 때onClick
이벤트를 기다리지 않고 즉시 실행된다
따라서 두 조건을 합치니
button
태그가 렌더링될 때 중괄호 안의 함수가 인자v
를 받아 바로 실행되고 반환값을 뱉는다- 함수가 실행될때마다
testState
가 변화하므로button
태그가 리렌더링된다 button
태그가 렌더링될 때 중괄호 안의 함수가 인자v
를 받아 바로 실행되고 반환값을 뱉는다- 함수가 실행될때마다
testState
가 변화하므로button
태그가 리렌더링된다 button
태그가 렌더링될 때 중괄호 안의 함수가 인자v
를 받아 바로 실행되고 반환값을 뱉는다- 함수가 실행될때마다
testState
가 변화하므로button
태그가 리렌더링된다...
컴포넌트 무한 리렌더링이 저 기본동작 때문인 줄 알고 e.preventDefault()
를 일일이 넣어준 거였는데 중괄호 안에 핸들러 함수가 아닌 변수를 넣어서 문제가 되었던 것이었다
onClick
이벤트에 핸들러 함수를 연결한 것이 아니라면 중괄호 안의 내용이 렌더링될 때 즉각 실행되는데, 함수 안에 해당 요소와 관련된 상태값을 바꾸는 매커니즘이 있다면 상태값이 바뀔 때마다 반복적으로 리렌더링이 되기 때문에 결과적으로 무한 리렌더링을 낳는 것이었다
해결 방법
...
const [testState, setTestState] = useState("");
...
const handleButtonClick = (v) => {
setTestState(v);
}
...
<button
className={testState}
onClick={() => handleButtonClick(v)}
>
리렌더링안된다옹
</button>
만약 인자값을 반드시 넣어줘야 하는 케이스라면 화살표 함수 (() ⇒ handleButtonClick(v)
) 로 한번 감싸 함수로 만들어주면 해결된다
e.preventDefault()
를 넣어서 해결이 되는 것처럼 보였던 이유는 e
를 받아오기 위해 화살표 함수를 사용했기 때문이었다
<button
className={testState ? "clicked" : ""}
onClick={handleButtonClick}
>
리렌더링안된다옹
</button>
인자 v
가 필요없는 경우엔, 이렇게 핸들러 함수를 괄호 없이 붙여준다
그러면 onClick
에 핸들러 함수가 제대로 연결되어 이벤트가 발생할 때에만 핸들러가 실행된다
const handleButtonClick = (v) => {
console.log(v);
}
...
<button
className={testState ? "clicked" : ""}
onClick={handleButtonClick("hi")}
>
리렌더링안된다옹
</button>
이 button
태그는 무한 리렌더링으로부터 안전한데, 그 이유인즉슨 handleButtonClick
핸들러가 button
태그와 관련된 상태값을 변화시키지 않기 때문에 리렌더링 자체가 되지 않는다
다만 위에서도 언급했듯 onClick에 함수가 연결된 것이 아니라 변수가 연결된 것이므로 아무리 버튼을 클릭해도 handleButtonClick
이 재실행되지 않는다
위의 코드를 실행시켜 보니 컴포넌트가 렌더링될 때만 실행되는 것을 볼 수 있었다
이 뒤로 아무리 버튼을 눌러 봐도 hi가 출력되지 않는다
결론
내가 이벤트에 연결하는 핸들러가 과연 함수인지 변수인지 잘 생각하고 넣자 ㅡ,,ㅡ;
handleButtonClick
은 일종의 함수 포인터이고, handleButtonClick(v)
는 반환값 변수라고 생각하자
그래도 오류가 왜 나는지 아는게 모르는채로 나는 것보다 백배천배 낫다
이유도 모른 채로 또 too many re-renders 받았으면 어버버할 뻔했다
참고자료
Too many re-renders. React limits the number of renders to prevent an infinite loop. - React Hooks
[React] Too many re-renders. React limits the number of renders to prevent an infinite loop.
'ClientSide > React' 카테고리의 다른 글
데이터 불러오기, Suspense (0) | 2022.05.13 |
---|---|
IntersectionObserver + 무한스크롤 (0) | 2022.05.12 |
Custom Hook (0) | 2022.05.09 |
CSS Module (0) | 2022.05.04 |
prevState 사용 이유 (0) | 2022.05.04 |