2026. 7. 18.
Rendered fewer hooks than expected, return 아래 Hook을 옮기세요
React 컴포넌트가 조기 반환 때문에 렌더마다 다른 수의 Hook을 호출할 때, return 아래 Hook을 옮기고 lint와 상태 전환으로 확인하는 순서다.

로딩 화면에서는 괜찮다가 데이터가 들어오는 순간 Rendered fewer hooks than expected가 뜬다면, 오류가 난 Hook만 고쳐서는 끝나지 않는다. 그 Hook보다 위에 있는 조건부 return을 먼저 찾는다. 한 렌더는 Hook을 만나기 전에 끝났고, 다음 렌더는 아래 Hook까지 실행했을 가능성이 크다.
해결 기준은 간단하다. useState, useEffect, useMemo 같은 일반 Hook을 조기 반환보다 위로 옮겨 모든 렌더에서 같은 순서로 호출한다. 조건이 필요하면 Hook 호출 자체가 아니라 Hook 안의 작업이나 반환할 JSX를 조건으로 나눈다.
return 아래 Hook을 컴포넌트 상단으로 옮긴다
React 공식 Rules of Hooks는 Hook을 컴포넌트 최상단에서, 어떤 조기 반환보다 먼저 호출하라고 설명한다. React는 렌더마다 첫 번째 Hook, 두 번째 Hook처럼 호출 위치로 상태를 연결한다. 조건에 따라 두 번째 Hook이 사라지면 그 뒤 상태의 자리를 맞출 수 없다.
아래 코드는 items가 없을 때 useMemo를 만나기 전에 끝난다. 데이터가 생긴 다음 렌더에서는 같은 컴포넌트가 Hook을 하나 더 호출한다.
function ProductList({ items }: { items?: Product[] }) {
const [query, setQuery] = useState('');
if (!items) return <p>불러오는 중</p>;
const visibleItems = useMemo(
() => items.filter((item) => item.name.includes(query)),
[items, query]
);
return <List items={visibleItems} />;
}
검색은 컴포넌트 안의 if만 보지 말고 return null, 로딩 JSX, 권한 없음 JSX도 포함한다. AI가 코드를 여러 번 고친 뒤 오류가 늘었다면 React 반복 렌더링을 끊는 확인 순서와 섞지 않는다. Maximum update depth는 상태 갱신 반복 문제이고, 이 오류는 Hook 호출 순서가 달라진 문제다.
두 렌더 경로를 같은 순서로 맞춘다
React의 state snapshot 설명처럼 각 렌더는 그 시점의 상태를 기준으로 JSX를 계산한다. 첫 렌더와 다음 렌더를 종이에 두 줄로 적으면 원인이 빨리 보인다.
- 로딩 렌더:
useState→return - 데이터 렌더:
useState→useMemo→ JSX 반환
두 줄이 다르면 아래쪽 Hook을 위로 옮긴다. 이때 Hook이 받을 값도 모든 렌더에서 안전해야 한다. items가 아직 없다면 빈 배열을 계산 기준으로 두면 된다.
function ProductList({ items }: { items?: Product[] }) {
const [query, setQuery] = useState('');
const safeItems = items ?? [];
const visibleItems = useMemo(
() => safeItems.filter((item) => item.name.includes(query)),
[safeItems, query]
);
if (!items) return <p>불러오는 중</p>;
return <List items={visibleItems} />;
}
safeItems를 렌더마다 새 배열로 만들면 useMemo 의존성이 매번 바뀔 수 있다. 이 예제에서는 계산이 작아 useMemo 자체를 빼고 일반 변수로 계산해도 된다. 핵심은 최적화 Hook을 지키는 일이 아니라 모든 렌더의 Hook 순서를 같게 만드는 것이다.
조건은 Hook 안쪽이나 결과 계산으로 옮긴다
로그인한 사용자에게만 요청하려고 if (isLoggedIn) useEffect(...)처럼 쓰면 조건부 Hook이 된다. React 공식 rules-of-hooks lint 문서는 useEffect는 항상 호출하고, 요청 조건을 콜백 안에서 검사하는 형태를 안내한다.
useEffect(() => {
if (!isLoggedIn) return;
void fetchProfile();
}, [isLoggedIn]);
상태 초기값이 조건에 따라 다르면 useState 호출을 두 갈래로 나누지 않는다. 초기값 표현식만 조건으로 계산한다.
const [mode, setMode] = useState(
isAdmin ? 'admin' : 'member'
);
예외로 React의 use API는 조건과 반복 안에서 호출할 수 있지만, try/catch 안에서는 쓸 수 없다. useState, useEffect, useMemo에 이 예외를 적용하면 안 된다. 평범한 화면 코드라면 Hook은 위에 고정하고 조건은 안으로 옮긴다고 기억하면 충분하다.
lint와 세 가지 상태 전환으로 끝낸다
코드를 옮긴 뒤 한 화면만 보고 끝내면 특정 분기에서 오류가 남을 수 있다. 다음 순서로 확인한다.
- 프로젝트의 lint 명령을 실행해
react-hooks/rules-of-hooks위반이 없는지 본다. - 로딩 상태를 열어 조기 반환 화면이 정상인지 확인한다.
- 데이터 있음과 데이터 없음 상태를 번갈아 열어 Hook 오류가 다시 뜨지 않는지 본다.
- Console을 비우고 같은 전환을 한 번 더 반복한다.
Console이 하얀 화면만 남기고 다른 오류를 먼저 표시한다면 React 빈 화면에서 Console로 원인을 찾는 법으로 첫 사용자 코드 줄부터 좁힌다. Hook 순서를 고쳤는데도 lint가 계속 실패하면 오류가 난 파일의 모든 use로 시작하는 호출을 검색하고, 콜백·반복문·조건문 안의 호출을 컴포넌트 또는 사용자 정의 Hook 최상단으로 옮긴다.
오늘의 완료 조건은 로딩·데이터 있음·데이터 없음 세 상태에서 같은 Hook 순서와 깨끗한 Console을 확인하는 것이다.
참고 출처
이 글은 AI 코딩과 개발 학습의 일반 정보 제공 목적입니다. 도구, 모델, 커리큘럼, 요금은 버전과 시점에 따라 달라질 수 있으므로 실습이나 도입 전 공식 문서와 최신 릴리스 노트를 확인하세요.
다음으로 읽을 기사
같은 흐름으로 이어 읽기 좋은 기사만 추려 보여줍니다.
첫 번째 댓글을 남겨보세요
여러분의 생각이 다른 독자에게 도움이 됩니다.
댓글 0
이 글을 읽은 독자들의 생각을 나눠보세요.