TechFeedTechFeed
Frontend

Maximum update depth, useEffect, 무한 루프 | 이펙트가 서로를 부르면?

Maximum update depth exceeded는 훅이 고장난 게 아니라 그린 뒤 이펙트가 상태를 바꾸고 그 상태가 다시 이펙트를 불러 한도를 넘긴 줄입니다. Too many re-renders와 구분하고, 객체 의존, 파생 값, 함수형 업데이트를 봅니다. React, Next.js, 한국 1인 개발자 기준. 2026년 9월 React useEffect·에러 185 문서.

by

Maximum update depth exceeded는 훅이 고장난 게 아니라, 그린 뒤 이펙트가 상태를 바꾸고 그 상태가 다시 이펙트를 불러 한도를 넘긴 줄입니다. 렌더 도중에 setState를 부르는 줄과 자리가 다릅니다.


화면이 멈추고 콘솔에 빨간 경고가 반복되는 일, 객체를 의존 배열에 넣거나 이펙트 안에서 같은 값을 새로 만들 때 한 번은 만납니다. 버튼을 고치기 전에 이펙트 의존부터 보세요.


컴포넌트를 통째로 갈아엎기 전에 의존 배열만 값으로 쪼개 보세요. 파생 값은 이펙트가 아니라 그리는 중에 계산합니다. 근거는 리액트 useEffect에러 185에 있습니다.


렌더 중 setState와 이펙트 루프는 다른 줄

리액트는 중첩 갱신이 한도를 넘으면 이 에러를 던집니다. 개발 빌드 메시지는 이펙트가 상태를 바꾸는데, 그 상태에 이펙트가 다시 기대면 루프가 된다고 안내합니다.


Too many re-renders는 그리는 도중에 setState를 호출한 줄입니다. onClick={fn()}처럼 렌더 몸통에서 바로 실행되면 그쪽입니다. 이 글은 그린 다음 이펙트가 서로를 부르는 칸입니다. 렌더 중 호출은 Too many re-renders 글을 보세요.


하이드레이션 미스매치는 서버 HTML과 첫 클라이언트 그림이 다른 줄이라 여기 루프와 무관합니다. 콘솔에 두 줄이 같이 있으면 원인을 나누세요. 미스매치는 하이드레이션 글입니다.


메시지언제 도나먼저 볼 코드
Maximum update depth exceeded이펙트 또는 업데이트 훅이 루프useEffect 의존 배열
Too many re-renders렌더 함수 안에서 setStateonClick={fn()}, 렌더 몸통
Hydration failed서버와 첫 그림이 다름Date, window, 잘못된 태그

의존 배열에 객체가 있으면 의심하세요 | 렌더마다 새 객체, 새 배열, 새 함수가 만들어지면 이펙트는 매번 바뀌었다고 봅니다. 그 이펙트가 setState를 하면 루프입니다.


리액트 이펙트가 상태를 바꿔 다시 이펙트를 부르는 루프 개념 이미지
이펙트가 바꾼 상태가 다시 그 이펙트의 의존이면 한도를 넘긴다

의존에 객체를 넣으면 매번 다른 값이다

리액트는 의존을 Object.is로 비교합니다. 내용이 같아도 렌더마다 새로 만든 객체는 다른 값입니다. useEffect(() => { setUser({ ...user, ok: true }) }, [user])처럼 새 객체를 넣으면 이펙트가 끝없이 돕니다.


공식 문서는 이펙트가 외부 시스템과 동기화할 때만 쓰라고 합니다. 화면용 값을 맞추려고 이펙트에서 setState를 반복하면 루프가 납니다. 부모에서 인라인 객체 props를 넘긴 뒤 자식이 그걸 의존에 넣어도 같습니다.


함수도 렌더마다 새로 생깁니다. 의존에 넣은 핸들러가 이펙트에서 상태를 바꾸면 같은 줄입니다. 그 함수를 이펙트 안으로 옮기거나, 정말 필요할 때만 useCallback으로 고정하세요.


객체를 의존에 넣어 루프가 나는 코드
// 매 렌더 user가 새 객체가 되어 이펙트가 다시 돈다 function Panel({ id }) { const [user, setUser] = useState({ id, flag: false }) useEffect(() => { setUser({ ...user, flag: true }) }, [user]) return <div>{user.id}</div> }

파생 값은 이펙트가 아니라 렌더에서 계산한다

리액트는 이펙트로 상태를 복제하지 말라고 합니다. 이미 있는 props나 state에서 만들 수 있는 값은 그리는 중에 계산합니다. 이펙트에서 setFiltered를 부르면 의존이 바뀔 때마다 한 바퀴 더 돕니다.


이전 값에 1을 더하는 이펙트도 그 숫자를 의존에 넣으면 루프입니다. 정말 마운트 한 번만 필요하면 배열을 비우고, 이전 값이 필요하면 함수형 업데이트 setN(n => n + 1)로 의존에서 빼세요.


서버 컴포넌트 파일에 훅을 넣은 줄은 여기 루프가 아닙니다. 지시자가 빠진 경우는 use client 글입니다. 이 글은 이미 클라이언트에서 이펙트가 돈 다음의 한도입니다.


하고 싶은 일이펙트대신
목록 필터setFiltered in effect렌더에서 filter()
props로 초기값props 바뀔 때 setStatekey로 리셋, 또는 props 직접 사용
외부 구독구독 후 setStateuseSyncExternalStore
마운트 한 번 fetch[] 의존요청 결과를 그 한 번만 set

파생 값은 렌더에서
function List({ items, query }) { const visible = items.filter((row) => row.name.includes(query)) return visible.map((row) => <li key={row.id}>{row.name}</li>) } function Counter() { const [n, setN] = useState(0) useEffect(() => { const id = setInterval(() => setN((v) => v + 1), 1000) return () => clearInterval(id) }, []) return <span>{n}</span> }
필터 결과를 이펙트 대신 렌더에서 계산하는 개념 이미지
이미 있는 값에서 만드는 데이터는 상태를 복제하지 않는다

어떤 이펙트인지 좁히는 법

개발 모드에서 경고 스택이 가리키는 컴포넌트부터 엽니다. 그 파일의 useEffect, useLayoutEffect를 잠시 주석 처리해 경고가 사라지는지 봅니다. 배열이 없는 이펙트는 매 렌더마다 돌아서 그 안에서 setState면 바로 루프입니다.


리액트 훅 ESLint가 의존을 채우라고 해서 객체를 넣었으면, 고치는 쪽은 객체를 안정화하는 것이지 경고를 무시하는 것이 아닙니다. 내용을 필드로 쪼개 user.id만 넣으면 비교가 값으로 바뀝니다.


프로덕션 미니파이 메시지는 에러 185로만 보입니다. 로컬 개발 빌드에서 같은 화면을 재현해야 어느 이펙트인지 나옵니다.


  • [ ] Too many re-renders와 메시지를 구분했다
  • [ ] 이펙트 의존에 객체, 배열, 인라인 함수가 있는지 봤다
  • [ ] 파생 값은 렌더 계산으로 옮겼다
  • [ ] 함수형 업데이트로 상태 의존을 빼 봤다
  • [ ] 배열 없는 이펙트에서 setState를 없앴다

실전에서 고르는 순서

스택의 컴포넌트에서 이펙트를 찾고, 의존이 렌더마다 바뀌는지 확인합니다. 객체면 필드로 쪼개고, 파생이면 이펙트를 삭제합니다. 그래도 남으면 렌더 몸통의 setState인지 다시 봅니다.


상태를 이펙트로 맞추는 습관이 있으면 루프가 반복됩니다. 외부 구독, 타이머, DOM만 이펙트에 남기세요. 나머지는 그리는 중에 계산하는 편이 짧습니다.


한국에서 넥스트 앱 라우터로 목록 필터와 카카오 프로필 객체를 화면에 넣는 경우, 프로필 객체 전체를 의존에 넣지 말고 id만 넣으세요. 객체를 자식으로 그대로 그리면 다른 에러가 납니다. 루프와 섞지 마세요.


이펙트 의존을 필드로 쪼개 루프를 끊는 순서 이미지
의존을 값으로 쪼개고, 파생 상태는 이펙트에서 뺀다

참고 자료


내부 연계: Too many re-renders, 하이드레이션 미스매치, use client


인용한 동작은 2026년 9월 공개 문서 기준입니다.


자주 묻는 질문

Too many re-renders와 무엇이 다른가요?

Too many re-renders는 그리는 도중에 setState를 호출한 줄입니다. Maximum update depth는 그린 다음 이펙트나 업데이트 훅이 상태를 바꿔 다시 그 훅을 부른 줄입니다. 콘솔 문장을 끝까지 읽으세요.


의존 배열을 비우면 해결되나요?

마운트 한 번만 필요하면 됩니다. 최신 props를 써야 하는데 비우면 오래된 값을 닫습니다. 객체 전체를 넣지 말고 id 같은 값만 넣거나, 파생이면 이펙트 자체를 삭제하세요.


ESLint가 의존을 넣으라고 합니다.

규칙을 끄기보다 의존을 안정화하세요. 객체는 필드로 쪼개고, 함수는 이펙트 안으로 옮기거나 필요한 값만 닫습니다. 경고를 무시하면 루프나 오래된 값이 남습니다.


setState를 이펙트에서 쓰면 안 되나요?

외부에서 온 데이터나 구독을 반영할 때는 씁니다. 이미 있는 state를 복제하거나 필터 결과를 또 저장하면 루프가 납니다. 그리는 중에 계산할 수 있으면 이펙트를 빼세요.


프로덕션에선 185만 보입니다.

미니파이 빌드는 긴 문장을 빼서 번호만 남깁니다. 로컬 개발 모드에서 같은 화면을 열면 컴포넌트 스택이 나옵니다.


useLayoutEffect도 같은가요?

의존이 매 렌더 바뀌고 그 안에서 setState면 같은 한도에 걸립니다. 페인트 전후만 다를 뿐 루프 원리는 같습니다.


Maximum update depth는 이펙트가 바꾼 상태가 다시 그 이펙트를 부르는 루프입니다. 객체 의존을 값으로 쪼개고, 파생은 렌더에서 계산하세요. 관련 글: Too many re-renders, 하이드레이션, use client.


Maximum update depthuseEffect무한 루프setState리액트넥스트프론트엔드이펙트의존 배열개발자

함께 보면 좋은 문제 해결

EXPLORE / Frontend

이어서 읽어보기

전체 토픽 둘러보기