Blog

React 19: Effect 내부에서 setState 호출 시 발생하는 경고(cascading render)와 해결 방법


React 19과 React Compiler 환경에서 프로젝트를 진행하면서
useEffect 내부에서 setState를 호출할 때 cascading render 경고가 발생했다.
이전 버전에서는 문제가 없던 코드이지만, React 19 Compiler는 초기 렌더 흐름을 더 엄격하게 검사한다.
해당 경고가 발생하는 원리와 해결 방법을 정리한다.

문제 코드#

const [isOpen, setIsOpen] = useState(false);

useEffect(() => {
  if (typeof window !== "undefined") {
    const saved = localStorage.getItem("SidebarOpen");
    if (saved !== null) {
      setIsOpen(saved === "true");
    }
  }
}, []);

많은 사람들이 오해할 수 있는 부분#

  1. effect가 반복 실행돼서 발생하는 문제는 아니다

useEffect(() => {}, [])는 기본적으로 한 번만 실행된다.
문제는 effect 실행 횟수가 아니라 렌더 후 상태가 뒤늦게 변경되는 구조다.

  1. localStorage 값에 따라 effect가 재실행되는 것은 아니다

localStorage의 내용은 effect 동작과 무관하다.

  1. cascading render가 무한 루프를 의미하는 것은 아니다

React 19에서 말하는 “cascading render”는
초기 렌더 후 추가 렌더가 이어지는 비효율적인 초기화 과정을 의미한다.

왜 경고가 발생하는가#

React Compiler는 초기 렌더 흐름이 다음과 같이 진행되는 경우를 비효율적이라고 판단한다.

초기 render (isOpen = false)
↓
commit
↓
useEffect 실행
↓
setIsOpen(...)
↓
re-render

초기 렌더에서 실제 상태가 확정되지 않은 채 commit이 발생하기 때문에
Compiler는 이를 “초기 상태가 늦게 확정되는 패턴”으로 보고 경고를 띄운다.

해결 방법: 초기 상태를 렌더 전에 확정하기#

localStorage에서 값을 읽는 로직을 useEffect가 아닌
useState의 lazy initializer로 옮기면 경고가 해결된다.

수정된 코드#

const [isOpen, setIsOpen] = useState(() => {
  if (typeof window !== "undefined") {
    const saved = localStorage.getItem("SidebarOpen");
    return saved === "true";
  }
  return false;
});

정리#

방식렌더 횟수React Compiler 평가
effect 안에서 setState최소 2번비효율적, 경고 발생 가능
useState lazy initializer1번가장 이상적인 패턴