▲ Next.js 14.2.26
- Environments: .env.local
Creating an optimized production build ...
✓ Compiled successfully
Failed to compile.
./src/components/calendar/MainCalendar.tsx
88:6 Warning: React Hook useEffect has a missing dependency: 'setSelectPlan'. Either include it or remove the dependency array. If 'setSelectPlan' changes too often, find the parent component that defines it and wrap that definition in useCallback. react-hooks/exhaustive-deps
./src/components/contacts/addContactForm/ContactTextField.tsx
54:7 Error: React Hook "useEffect" cannot be called inside a callback. React Hooks must be called in a React function component or a custom React Hook function. react-hooks/rules-of-hooks
info - Need to disable some ESLint rules? Learn more here: https://nextjs.org/docs/basic-features/eslint#disabling-rules
Linting and checking validity of types . ELIFECYCLE Command failed with exit code 1.
에러 상황
useEffect를 react-hook-form의 <FormField /> 컴포넌트 안에서 사용했더니 위와 같은 에러가 발생했다.
처음엔 컴포넌트 내부에 있으니까 괜찮을 줄 알았는데, 알고 보니 Hook은 반드시 컴포넌트의 최상단에서만 호출되어야 한다는 규칙이 있었다!
원인
React는 Hook의 실행 순서를 추적해서 상태 관리를 한다고한다.
그래서 useEffect, useState 같은 Hook은 조건문, 반복문, 콜백 함수 안에서 사용하면 안 된다.
내 경우에는 FormField의 render prop으로 넘긴 콜백 안에 useEffect가 들어가 있어서 생긴 문제였다.
해결 방법
useEffect를 render 함수 바깥으로, 즉 ContactTextField 컴포넌트의 최상단에 위치시키니까 정상적으로 작동했다.
Hook은 항상 컴포넌트가 시작되자마자 호출되어야 하며, 조건 없이 한 번만 실행되는 구조여야 한다는 걸 명확히 알게 됐다.
느낀 점
다행스럽게도 ESLint가 Hook 규칙을 강하게 잡아준 덕분에 잘못된 구조를 빠르게 발견할 수 있었다.
다른 작업을 하면서 뒤늦게 PR을 올리기전에 발견한 build 에러라 당황했고 작업하면서 자주 빌드를 해봐야겠다는 경험을 하게 되었다. 앞으로는 Hook은 최상단에서 호출한다는 원칙을 명심하자!