치춘짱베리굿나이스

Too many re-renders 오류 본문

ClientSide/React

Too many re-renders 오류

치춘 2022. 5. 7. 02:08

😡

많은 프로젝트에서 나를 괴롭혔던 오류이다

툭하면 발생하는데 브라우저에는 컴포넌트가 하나도 렌더링되지 않고 흰 배경만 덩그러니 남아 있어서 더 킹받았던 오류

발생하는 이유도 제대로 몰랐는데 내 코드 리팩토링하면서 이유를 찾아버려 정리하기로 했따

왜 나를 괴롭히는가?

처음에는 각 태그에서 발생하는 이벤트의 기본 동작이 반복적으로 발생하는 줄 알고 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를 받는다고 가정하자

  1. handleButtonClick은 클릭할 때마다 testState를 변화시킨다
  2. 각 버튼들은 testState에 따라 클래스명이 변화하므로 testState가 변할 때마다 리렌더링된다
  3. 이때 중괄호 안에 handleButtonClick(v)를 바로 집어넣는다는 것은 실질적으로 onClick에 함수를 넣은 게 아니라 handleButtonClick 함수에 v를 넣고 실행시킨 반환값을 이용하겠다는 의미가 된다
  4. 중괄호 안에 함수를 넣은 게 아니라 함수의 반환값을 넣었으므로, onClick에 함수가 걸린 게 아니라 변수가 걸린 게 된다 ⇒ 렌더링될 때 onClick 이벤트를 기다리지 않고 즉시 실행된다

따라서 두 조건을 합치니

  1. button 태그가 렌더링될 때 중괄호 안의 함수가 인자 v를 받아 바로 실행되고 반환값을 뱉는다
  2. 함수가 실행될때마다 testState가 변화하므로 button 태그가 리렌더링된다
  3. button 태그가 렌더링될 때 중괄호 안의 함수가 인자 v를 받아 바로 실행되고 반환값을 뱉는다
  4. 함수가 실행될때마다 testState가 변화하므로 button 태그가 리렌더링된다
  5. button 태그가 렌더링될 때 중괄호 안의 함수가 인자 v를 받아 바로 실행되고 반환값을 뱉는다
  6. 함수가 실행될때마다 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

 

Too many re-renders. React limits the number of renders to prevent an infinite loop. - React Hooks

I'm new to React hooks here, I've a sidebar I want to update the selecetedIndex to the index selected for that ListItem i.e., when I select an item I want to update selectedIdx to index to show it

stackoverflow.com

[React] Too many re-renders. React limits the number of renders to prevent an infinite loop.

 

[React] Too many re-renders. React limits the number of renders to prevent an infinite loop.

[Error] 단순히 props로 받은 데이터를 onClick 이벤트가 발생했을 때 파라미터로 넘겨줄 생각이었다. 근데 뜬금없는 에러가 발생.. [문제 파악] 상단 이미지를 보면 onClick 이벤트가 일어났을 때 handleCl

born-dev.tistory.com

 

'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
Comments