시작하며

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 함수 컴포넌트 또는 커스텀 훅에서만 호출이 가능하다.
'JavaScript > React' 카테고리의 다른 글
React에서 요소에 접근하는 방법, useRef vs getElementById (0) | 2024.05.19 |
---|---|
react에서 요소의 width와 height 구하기 (0) | 2024.05.19 |