[ERROR] React Hooks must be called in a React function component or a custom React Hook function.

728x90

시작하며

React Hooks must be called in a React function component or a custom React Hook function 오류화면

React를 사용하다 보면 "React Hooks must be called in a React function component or a custom React Hook function."이라는 ESLint 오류를 접할 수 있습니다. React를 처음 사용하며 블로그나 문서를 통해 eslint를 설치하거나, Create React App으로 React를 공부하기 시작했다면 어느 순간 만나게 되는 오류일 것입니다.

 

이 오류는 React 애플리케이션의 무결성과 예측 가능성을 유지하는 데 매우 중요합니다. 이 오류가 무엇을 의미하는지, 왜 발생하는지, 그리고 어떻게 해결할 수 있는지 자세히 알아보겠습니다.

 

React Hooks란 무엇인가요?

 이 오류가 발생했다면 이미 훅에 대한 내용을 알고 있겠지만, "use"로 시작하는 함수들을 리액트에서는 "Hooks"라고 부릅니다. React Hooks는 함수 컴포넌트에서 React 상태와 생명주기 기능에 ""을 걸 수 있게 해주는 특별한 함수들입니다. 가장 많이 사용되는 훅으로는 useState, useEffect, useContext, useReducer 등이 있습니다.

 

왜 이 오류가 발생하나요?

이 오류는 React가 훅을 사용하는 위치와 방법에 대해 엄격한 규칙을 가지고 있기 때문에 발생합니다. 이러한 규칙은 훅이 예상대로 작동하도록 보장하는 데 필수적입니다. 주요 규칙은 다음과 같습니다.

 

훅은 최상위 레벨에서만 호출해야 한다

function Counter() {
  // ✅ Good: 함수형 컴포넌트의 최상단에서 hooks 호출
  const [count, setCount] = useState(0);
  // ...
}

function useWindowWidth() {
  // ✅ Good: 커스텀 훅의 최상단에서 hooks 호출
  const [width, setWidth] = useState(window.innerWidth);
  // ...
}

 

 리액트 훅은 컴포넌트가 랜더링 될 때만 최상위 레벨에서 호출할 수 있습니다. 리액트 컴포넌트는 상태(state)나 속성(props)이 변경될 때마다 다시 랜더링되는데, 이 랜더링 과정에서 훅이 호출되는 순서를 기반으로 상태를 관리합니다. 따라서 훅은 항상 동일한 순서로 호출되어야 상태가 올바르게 초기화되고 업데이트됩니다. 

 

그렇다면 어떤 경우에 훅의 호출 순서가 바뀌게 될까요?

  •  반복문
function Bad() {
  for (let i = 0; i < 10; i++) {
    // 🔴 Bad: 루프 내부에서 훅을 사용
    const theme = useContext(ThemeContext);
  }
  // ...
}
  • 조건문
function Bad({ cond }) {
  if (cond) {
    // 🔴 Bad: 조건문 내부에서 훅을 사용하는 경우
    const theme = useContext(ThemeContext);
  }
  // ...
}
  • 중첩 함수
function Bad() {
  function handleClick() {
    // 🔴 Bad: 중첩 함수 내부
    const theme = useContext(ThemeContext);
  }
  // ...
}
  • try/catch/finally
function Bad() {
  try {
    // 🔴 Bad: try/catch/finally 내부에서 사용하는 경우
    const [x, setX] = useState(0);
  } catch {
    const [x, setX] = useState(1);
  }
}
  •  early return 
// 🔴 Bad: early return 이후
function Bad({ cond }) {
  if (cond) {
    return;
  }

  const theme = useContext(ThemeContext);
  // ...
}
  • 클래스 컴포넌트
class Bad extends React.Component {
  render() {
    // 🔴 Bad: 클래스 컴포넌트
    useEffect(() => {})
    // ...
  }
}
  • 일반 자바스크립트 함수
function Bad() {
  function handleClick() {
    // 🔴 Bad: 일반 자바스크립트 함수 내부
    const theme = useContext(ThemeContext);
  }
  // ...
}

 

정리하자면, 훅은 항상 동일한 순서로 호출되어야 상태가 올바르게 초기화되고 업데이트됩니다. 순서가 변경되는 곳에서 훅을 호출하면 안됩니다.

  • 🔴 조건문이나 반복문에서 Hook을 호출하면 안됩니다.
  • 🔴 early return 다음에 Hooks를 호출하면 안됩니다.
  • 🔴 try/catch/finally 블록 내부에서 Hook을 호출하면 안됩니다.
  • 🔴 클래스 컴포넌트에서 Hook을 호출하면 안됩니다.
  • 🔴일반 자바스크립트 함수에서 Hook을 호출하면 안됩니다.

훅은 React 함수 컴포넌트 또는 커스텀 훅에서만 호출해야 합니다. 즉, 일반 자바스크립트 함수, 클래스 컴포넌트 또는 기타 비-React 함수에서 훅을 호출할 수 없습니다.

 

해결 방법

해결 방법은 간단합니다! 설명의 가장 위에 있듯이 훅을 가장 상위에서 호출하면 됩니다.

// 🔴 Bad: 루프 내부에서 훅을 사용
function Counter() {
  for (let i = 0; i < 10; i++) {
      const [count, setCount] = useState(0);
      console.log(count);
  }
  // ...
}
// ✅ Good: 함수형 컴포넌트의 최상단에서 hooks 호출
function Counter() {
  const [count, setCount] = useState(0);
  
  for (let i = 0; i < 10; i++) {
      console.log(count);
  }
  // ...
}

 

 

정리

  • 훅은 항상 동일한 순서로 호출되어야 상태가 올바르게 초기화되고 업데이트된다.
  • 따라서 조건문, 반복문, 중첩 함수, try/catch/finally 블록, 클래스 컴포넌트, 일반 자바스크립트 함수 내부에서는 훅을 호출하면 안 된다.
  • 훅은 React 함수 컴포넌트 또는 커스텀 훅에서만 호출이 가능하다.
728x90