Maru@maru
Dev Hub
React 19로 마이그레이션한 뒤에 갑자기 브라우저가 멈추거나 Maximum update depth exceeded 에러를 마주치셨다면, 십중팔구 ref 합성 과정에서 터진 지뢰일 확률이 높습니다. 최근 react-select나 여러 프리미티브 컴포넌트들을 조합해서 쓸 때 이런 현상이 자주 보고되고 있는데요. 범인은 바로 React 19의 새로운 ref 클린업 모델과 불안정한 ref 합성 함수의 조합입니다.
React 19부터는 ref 콜백이 클린업 함수를 반환할 수 있게 되면서, 매 렌더링마다 ref 콜백 함수의 참조값(identity)이 바뀌면 React가 이전 ref를 해제하기 위해 null을 전달했다가 다시 새 ref 함수에 엘리먼트를 전달하는 과정을 거칩니다. 문제는 useComposedRefs 같은 헬퍼를 쓰면서 매 렌더링마다 합성 ref 함수를 인라인으로 새로 만들 때 발생해요. 매 렌더링마다 ref가 떨어졌다 붙었다 하는 현상이 반복되는 거죠.
여기서 진짜 문제는 그 합성 ref 내부에서 컴포넌트 상태를 바꾸는 로직이 들어있을 때 발생합니다. ref가 새로 붙으면서 상태를 업데이트하고, 상태가 바뀌니 다시 렌더링이 돌고, 렌더링이 도니 ref 함수 참조값이 바뀌어 ref가 다시 떨어졌다 붙으면서 상태를 또 바꾸고... 결국 걷잡을 수 없는 무한 렌더링 굴레에 빠지게 됩니다. Radix UI 같은 곳은 최근 패치를 올렸지만 여전히 외부 생태계 라이브러리들 중에는 이 이슈가 남아있는 경우가 꽤 있어요.
이 문제를 피하려면 외부 라이브러리 업데이트를 체크해 보거나, 직접 구현한 합성 ref 헬퍼가 있다면 꼭 useCallback이나 useMemo로 참조 무결성을 유지해 줘야 합니다. React 19 도입을 준비 중인 팀이라면 의존성 라이브러리들이 ref를 어떻게 합성하고 있는지 한 번씩 훑어보시는 것을 추천해 드려요 ㅋㅋ