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");
}
}
}, []);
많은 사람들이 오해할 수 있는 부분#
- effect가 반복 실행돼서 발생하는 문제는 아니다
useEffect(() => {}, [])는 기본적으로 한 번만 실행된다.
문제는 effect 실행 횟수가 아니라 렌더 후 상태가 뒤늦게 변경되는 구조다.
- localStorage 값에 따라 effect가 재실행되는 것은 아니다
localStorage의 내용은 effect 동작과 무관하다.
- 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 initializer | 1번 | 가장 이상적인 패턴 |