Troubleshooting Archive/React

[React] 무한 렌더링 에러

latteeea 2026. 4. 2. 17:14

What the error

healthEditPage.tsx?t=1773280685758:188 Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn't have a dependency array, or one of the dependencies changes on every render.

 

React로 작업하는데 콘솔에서 에러 무한 증식한 적은 처음이라 어리둥절... 

 

🐛 문제

Cursor로 게시물 작성 페이지처럼 Edit하는 페이지를 만들었는데 페이지 진입하자마자 빨간색 렌더링 횟수 부분 무한으로 늘어남 

(-> setState가 끝없이 반복 호출되는 문제 발생)

=> state 업데이트 -> 렌더링 -> state 업데이트 -> 렌더링... 가 계속 반복되고 있는 것

 

🧠 원인

const handlePublish = useCallback(() => {
  // ...
  healthEdit?.setPublishLoading?.(true);
  // ...
  healthEdit?.setPublishLoading?.(false);
}, [editor, navigate, title, thumbnailUrl, isPublic, postId, healthEdit]);
//

 

 

- Cursor가 handlePublish 라는 업로드 함수를 useCallback* 으로 구현했는데

- 이 dependency에 healthedit라는 Context 객체* 가 들어가있는 상태. 

- (여기서 문제) handlePublish 가 실행될때 healthEdit의 setHandler가 호출되면서 컨텍스트 객체인 helathEdit 상태가 변경됨 

 

healthEdit가 실행되면? -> 이를 의존성으로 가지고 있는 handlePublish도 재생성됨 -> handlePublish가 생성되면 healthEdit 상태가 변경 -> 또다시 handlePublish 실행 ... 무한 반복 

즉, healthEdit가 매 렌더 생성되는 객체라서 handlePublish도 매번 새로 만들어지면서 새로운 함수가 되는 것이 원인이었다 

 

* usecallback : 의존성 배열(코드 기준에서는 editor, navigate, title... 등)에 넣은 값이 변하지 않는 한, 기존에 만들어둔 함수를 그대로 쓰도록 하는 것 (재생성 방지)

* Context 객체 : 전역으로 관리되는 상태 보관소로, 값이 바뀔 경우 리액트가 반응함 (화면이 바뀌는 등)

 

📜 해결

healthEdit가 함수 생성 부분과 연결되어 있는 dependency array에서 제거하기!

-> 그럼 healthEdit는 어떻게 참조하느냐?

healthEdit.setPublishLoading은 써야하기 때문에 -> ref 기반* 으로 해결 

useRef를 생성해서 렌더마다 최신 컨텍스트 함수를 저장하지만, 리렌더는 발생하지 않도록 

-> callback 부분의 dependency 인자에서 healthEdit를 제거하고 참조를 안정화하긔! 

const handlePublish = useCallback(() => {
  setPublishLoadingRef.current?.(true);
  // ...
  setPublishLoadingRef.current?.(false);
}, [editor, navigate, title, thumbnailUrl, isPublic, postId]);
//   healthEdit 제거

const setPublishLoadingRef = useRef(healthEdit?.setPublishLoading);
setPublishLoadingRef.current = healthEdit?.setPublishLoading;

 => healthEdit 변경되면 -> ref.current 값만 갱신 -> handlePublish는 그대로 (effect 실행 x)

 

* useRef : 화면을 재생성하지 않고 상태 저장만 하고 싶을 때 쓸 수 있는(or HTML DOM에 직접 접근하고 싶을때),, 상태가 바뀌면 반응하는 useState와 대비되는 개념이다. 즉, 둘 다 상태를 참조하지만 useState는 화면 UI 대응용으로 사용가능하고, useRef는 데이터 저장 측면에서 고려하면 좋다. 

 

💻 정리

React 개념

1.useCallback

- 함수 재생성을 막고 참조를 유지하는 hook

- React는 기본적으로 렌더링할 때 함수도 새로 만드는데

- 성능 최적화를 위해 dependency가 바뀌지 않는 한 같은 함수의 재생성을 막는 것 

2. useRef

- 렌더와 상관없이 값을 저장하는 전역 container 

- 값을 변경할 시 -> 리렌더가 일어나지 않음 

3. useState

- 상태(값)를 관리하며, 상태가 바뀔경우 렌더링이 발생함 

- 상태가 바뀌어도 렌더가 발생하지 않는 useRef와 비교 가능 

 

기억하기 좋은 패턴

Stable Callback + ref Pattern

- 콜백은 안정적으로 유지하되, 최신 값은 ref로 읽기

- 보통 editor handler, event listener, websocket callback, context handler 등록 등에서 사용됨 

=> 컨텍스트 객체는 렌더마다 바뀔 수 있기 때문에 dependency에 넣으면 위험함!
대신 필요한 값만 useRef로 보관해 콜백을 안정적으로 유지하는 것이 좋음