[TIL] Invalid hook call. Hooks can only be called inside of the body of a function component. & You might be breaking the Rules of Hooks

G E Lee·2023년 1월 26일
0

react

목록 보기
4/5
post-thumbnail

토이 프로젝트를 진행하면서 auth 관련하여 로그아웃 함수를 모듈로 분리하여 사용해야할 경우가 생겼다.
하지만 콘솔 창에서 React Hooks must be called in the exact same order in every component render 라는 에러를 자꾸 뱉어냈다.
딱 보기에도 React에 대한 기본 개념이 부족하여 발생한 에러 같았다.
에러 해결 방법과 hook에 대한 이해한 내용을 기록해 놓는다.

에러가 난 상황

1. firebase에서 auth를 import해서 가져옴
2. react-router-dom에서 navigate()를 변수 navi로 선언, path는 "/"
3. 해당 함수가 실행되면 auth 로그아웃 -> navi("/") 변수 실행
4. 💥 에러 배출!

커스텀 훅을 직접 만든 것이 아니라 모듈 내부에서 훅을 가져와서 사용하고 있어서 오류가 나는 것이었다.
auth 로그아웃 + navi("/") 기능을 내부 함수로 따로 감싸도 보고, navigate() 함수 자체와 auth를 props로 받아오는 함수로 수정 해보았지만 모두 같은 에러를 뱉어냈다.
그래서 react 공식문서를 찾아보았다.


React Hook의 규칙

React 공식 문서에 Hook에 대한 규칙이 적혀있다.


조건 1. Hook은 v16.8에 추가된 기능, 버전이 다를 경우 에러 발생 할 수 있음!

Hook은 React 16.8에 새로 추가된 기능입니다. Hook은 class를 작성하지 않고도 state와 다른 React의 기능들을 사용할 수 있도록 해줍니다.

package.json 파일 안에 react와 react-dom 버전이 16.8보다 낮을때 생기는 오류일 수도 있으니, 버전이 낮아서 생기는 오류라면 업그레이드 해주면 된다.
나의 경우는 버전이 훨씬 높아서 이 케이스는 제외하였다.

package.json

"react": "^18.2.0",
"react-dom": "^18.2.0",

조건 2. 함수 컴포넌트에서 사용한다.

React 함수 컴포넌트 내에서만 호출한다.

클래스 컴포넌트가 아닌 함수 컴포넌트에서 사용해야한다.

항상 사용하던 Hook에 답이 있었다

export default function App() {
  const [string1, setString1] = useState("text");
  const [string2, setString2] = useState(" 변경");
  const changeStringFunction = () => {
    const changeStr1 = string1 + string2;
    const changeStr2 = string1 + string2;
    setString1(changeStr1);
    setString2(changeStr2);
  };
  return (
    <>
      <div>{string1}</div>
      <div>{string2}</div>
      <button onClick={changeStringFunction}>버튼</button>
    </>
  );
}

위의 예제에서 useStateReact Hook이다.
useState는 changeStringFunction 함수 컴포넌트 안에서 사용되고 있다. 해당 함수 바깥에 선언되어 함수 내부에서 재사용되고 있는 것이다.


조건 3. 최상위 레벨에서만 호출한다.

최상위 레벨에서 사용하고, 반복문, 조건문 혹은 중첩된 함수 내에서 Hook을 호출하지 마세요.

Hook들은 사용된 지점에서 실행될 순서를 기억한다. 위와같은 예제에서도 useState는 첫 번째 배열로 "text"를 먼저 저장하고 그 이후 두 번째 배열에 " 변경"을 저장한다. 이렇게 순서대로 저장된 Hook은 순서를 변경하면 저장된 순서와 다르게 실행되므로 오류가 날 수 밖에 없다.
이 Hook을 조건문이나 반복문안에서 호출한다면, 어떠한 조건에선 첫 번째 Hook이 실행되고 어떠한 조건에선 두 번째 Hook이 실행된다. 저장된 순서가 엉망이 되는 것이다.
그래서 React Hook은 항상 최상위 레벨에서만 호출해야한다.


그래서? 에러난 코드 리팩토링! 📙

에러 코드

LogOut.module.tsx 에 useNavigate를 사용하여 로그아웃 이후 '/'로 이동하는 함수를 만들었다.
그리고 해당 함수는 modal.tsx에서 사용되고 있었다.

LogOut.module.tsx

import { useNavigate } from "react-router-dom";
import { auth } from "../../firebase";

/** logout module */
export function LogOut() {
  const navigate = useNavigate();
  auth.signOut();
  navigate("/");
}

Modal.tsx

import { LogOut } <from "../auth/LogOut.module";

const Modal = () => {
  const ModalLogOut = () => {
    LogOut();
  };
  
  return (
    <>
      <div>OOO님 반갑습니다.</div>
      <button onClick={LogOut}>로그아웃</button>
	</>
}
export default Modal

코드 리팩토링

LogOut.module.tsx에 useNavigate를 변수로 호출한 것이 문제였다. 어떤 값을 변경하는 함수일뿐 함수 컴포넌트가 아니기 때문이다.
그래서 Modal.tsx에서 props로 Navigate()를 변수로 전달해주었다.

LogOut.module.tsx

import { NavigateFunction } from "react-router-dom";
import { auth } from "../../firebase";

/** logout module */
export function LogOut(navigate: NavigateFunction) {
  auth.signOut();
  navigate("/");
}

Modal.tsx

import { useNavigate } from "react-router-dom";
import { LogOut } from "../auth/LogOut.module";

const Modal = () => {
  const ModalLogOut = () => {
    LogOut(navigate);
  };
  
  return (
    <>
      <div>OOO님 반갑습니다.</div>
      <button onClick={ModalLogOut}>로그아웃</button>
	</>
}
export default Modal

드디어 에러가 없어졌다! 휴!~~
이제 다른 곳에서도 로그아웃 함수를 사용할 수 있게 되었다.



참고
https://ko.reactjs.org/docs/hooks-rules.html
https://kwangsunny.tistory.com/10

profile
배움은 끝이 없다

0개의 댓글