React: 'Rendered more hooks than during the previous render' 훅 호출 순서 에러 해결법

React Rendered more hooks 에러 원인 및 훅 호출 규칙 해결 가이드
3초 요약: 이렇게 고치세요
  • 원인: React가 훅을 이름이 아니라 "호출된 순서"로 추적하는데, 조건문·조기 반환 때문에 렌더링마다 훅 호출 개수가 달라져서 발생
  • 해결: 모든 훅 선언을 컴포넌트 최상단, 조기 반환(return)보다 위로 이동 / 조건은 훅 바깥이 아니라 훅 내부 로직으로 넣기
  • 주의: 훅 자체를 if로 감싸는 건 금지지만, 훅 내부에서 조건문을 쓰는 건 정상적으로 허용되는 패턴
useEffect(() => {
  if (isLoggedIn) analytics.logPage(); // 조건은 훅 안으로
}, [isLoggedIn]);
↓ 왜 이런 에러가 나는지 원리 보기

React 컴포넌트에서 조건에 따라 다른 화면을 먼저 보여주려고 조기 반환(early return)을 쓰다 보면 'Rendered more hooks than during the previous render' 에러를 만나는 경우가 있습니다. 이 에러가 왜 발생하는지, 그리고 훅 호출 규칙을 지키면서 문제를 해결하는 방법을 정리합니다.


왜 'Rendered more hooks' 에러가 발생하는가

이 에러의 근본적인 원인은 "리액트가 훅의 순서와 개수를 기억하는 방식"에 있습니다. 리액트 내부적으로 컴포넌트 내의 훅들을 상태 기억을 위해 링크드 리스트(Linked List) 구조로 관리합니다. 즉, 리액트는 훅을 고유한 이름이 아닌 "호출되는 순서"대로 매칭하여 값을 추적합니다. 조건문(if)이나 조기 반환(return)문 때문에 특정 렌더링에서 훅의 실행 순서가 뒤바뀌거나 누락되면, 리액트는 이전 상태와 매칭하는 데 실패하여 즉시 렌더링 크래시(컴파일 거부)를 발생시킵니다. 로딩 중일 때만 조기 return을 걸어두는 패턴을 쓰다가 이 에러를 처음 겪는 경우가 많습니다.

렌더링 별 훅 호출 체인 불일치

1차 렌더링 (통과) 1. useState (이름)
2. useEffect (로그인)
-> 총 2개 훅 순차 등록 완료
2차 렌더링 (에러 발생) 1. useState (이름)
(조건문에 의해 useEffect 건너뜀)
-> 훅 개수 불일치로 즉시 크래시

훅 규칙 위반 에러를 해결하는 2가지 올바른 방법

방법 1: 조기 반환(Early Return)보다 윗선으로 훅 선언부 올리기

조건문이나 예외 처리 반환문이 등장하기 전에 모든 훅의 선언을 컴포넌트 최상단에 배치합니다. 이렇게 하면 렌더링 결과가 조기 반환되더라도 훅의 호출 횟수는 항상 동일하게 유지됩니다.

[에러가 나는 코드]

export default function UserProfile({ userId }: { userId: string }) {
  // 1. 첫 번째 훅 선언
  const [user, setUser] = useState(null);

  // 에러 유발: 로딩 시 조기 반환(return) 발생
  if (!userId) {
    return <p>사용자 ID가 없습니다.</p>;
  }

  // 2. 조기 반환 아래에 훅이 있어 렌더링에 따라 실행 횟수가 달라짐
  useEffect(() => {
    fetchUser(userId).then(data => setUser(data));
  }, [userId]);

  return <div>{user.name}</div>;
}

[올바른 해결 코드]

export default function UserProfile({ userId }: { userId: string }) {
  // 1. 모든 훅의 선언부를 컴포넌트 최상단으로 정렬
  const [user, setUser] = useState(null);

  useEffect(() => {
    if (userId) { // 조건문은 훅 내부로 이동
      fetchUser(userId).then(data => setUser(data));
    }
  }, [userId]);

  // 2. 조기 반환(return)은 훅의 호출이 끝난 아랫선에 배치
  if (!userId) {
    return <p>사용자 ID가 없습니다.</p>;
  }

  return <div>{user ? user.name : "로딩 중..."}</div>;
}

방법 2: 훅 내부에서 조건문을 사용해 제어하기

리액트 훅 자체(useState, useEffect 등)를 if 문으로 감싸는 것은 금지되지만, "훅 내부에 조건문을 삽입하는 것"은 정상적으로 허용되고 권장되는 방식입니다.

// 에러 유발: 훅 자체를 조건문으로 래핑함
if (isLoggedIn) {
  useEffect(() => {
    analytics.logPage();
  }, []);
}

// 올바른 해결법: 훅은 무조건 호출되게 하고 내부 로직에 조건 부여
useEffect(() => {
  if (isLoggedIn) {
    analytics.logPage();
  }
}, [isLoggedIn]);

권한에 따라 화면 일부를 숨기는 관리자 페이지를 만들 때, "권한 없으면 바로 return" 패턴을 습관처럼 컴포넌트 맨 위에 넣었다가 이 에러를 처음 만났습니다. 에러 메시지만 보고는 뭐가 문제인지 감이 안 잡혀서 한참 헤맸는데, 콘솔에 찍힌 훅 순서를 하나씩 세어보고 나서야 조기 반환 아래에 있던 useEffect가 렌더링마다 실행되거나 안 되거나 하고 있었다는 걸 알았습니다.


요약: 리액트 훅 호출의 철칙

정리하면, 조건문이나 반복문 안에서 훅을 바로 호출하지 말고 조건은 훅 내부 블록이나 콜백 안으로 옮겨 호출 횟수를 항상 일정하게 유지해야 합니다. 조기 반환(return) 아래에 훅을 두지 말고 모든 훅 선언을 컴포넌트 본문 최상단에 모아두면 렌더링 분기와 상관없이 호출이 보장됩니다. 훅은 중첩 함수가 아닌 오직 React 컴포넌트와 Custom Hook의 최상위에서만 호출해야 Hook Chain의 무결성이 유지됩니다.

공식 참고 레퍼런스

리액트 훅 호출 규칙에 대한 공식 문서는 아래에서 확인할 수 있습니다.

호마다의 웹개발 팁

조건부 렌더링이 많아질수록 훅 순서를 지키기 까다로워지므로, 컴포넌트가 복잡해지면 조건 로직을 커스텀 훅으로 분리하는 것도 고려해볼 만합니다.

댓글