Invalid hook call - hooks을 모듈화 할 수 있는가?

33961 단어 ReactReact

hooks 모듈화

Header.jsx

import { useState } from "react";
import { movePath } from "../Utils";
import MenuIcon from "@mui/icons-material/Menu";
import SearchIcon from "@mui/icons-material/Search";
import NotificationsIcon from "@mui/icons-material/Notifications";
import AccountCircleIcon from "@mui/icons-material/AccountCircle";

const SearchBar = () => {
  const [search, setSearch] = useState("");

  const handleSubmitSearch = (e) => {
    e.preventDefault();
    console.log(search); // 검색 창으로 이동해야 함.
    movePath(`/search?keyword=${search}`);
    setSearch("");
  };
  const handleChangeSearch = (e) => {
    setSearch(e.currentTarget.value);
  };
  <div>
    <form onSubmit={handleSubmitSearch}>
      <input
        onChange={handleChangeSearch}
        type="text"
        maxLength="12"
        className="search"
        value={search}
        placeholder="게시물 제목 검색"
      />
      <button onClick={handleSubmitSearch}>검색</button>
    </form>
  </div>;
};

const Header = ({ handleOpenMenu, isSearch }) => {
  return (
    <div className="mainpage-header">
      <MenuIcon sx={{ color: "white" }} onClick={handleOpenMenu} />
      <SearchIcon sx={{ color: "white" }} onClick={() => movePath("/search")} />
      <img src="assets/headerLogo.svg" alt="header-logo" />
      <NotificationsIcon sx={{ color: "white" }} />
      <AccountCircleIcon
        sx={{ color: "white" }}
        onClick={() => movePath("/profile")}
      />

      {isSearch && <SearchBar />}
    </div>
  );
};
export default Header;

movePath.jsx

import { useNavigate } from "react-router-dom";

const movePath = (path) => {
  const navi = useNavigate();
  console.log(path);
  navi(path);
};

export default movePath;

다음처럼 useNavigate를 사용하는 부분이 중복되어서 모듈에서 useNavigate를 할당받아서 이동을 대신 해주려고 했다. 그런데...

Invalid hook call

Uncaught Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app

테스트를 해보니 에러를 뿜는다. 파파고의 힘을 빌려보니 이렇게 말하더라.

  • 잘못된 후크 호출입니다. 후크는 함수 구성요소의 본체 내부에서만 호출할 수 있습니다. 이 문제는 다음 이유 중 하나로 인해 발생할 수 있습니다.
  1. React와 렌더러의 버전이 일치하지 않을 수 있습니다(예: React DOM).
  2. hooks 룰을 어기는 것일 수도 있습니다.
  3. 동일한 앱에 React 사본이 두 개 이상 있을 수 있습니다.

hooks 룰 - Only Call Hooks from React Functions

https://reactjs.org/docs/hooks-rules.html

금방 해답이 나왔다. 훅은 항상 리액트 컴포넌트 내부에서만 호출해야 되었던 것이다. 바로 밑에서도 일반 자바스크립트 코드에서 호출하지 말라고 명시하고 있다. 컴포넌트에서 호출하거나 커스텀 훅으로 호출하는 방법 이외로 호출할 수 없는 것이다. 그렇다면 방법은 간단해진다.

해결

Header.jsx

import { useState } from "react";
import { useNavigate } from "react-router-dom";
import { movePath } from "../Utils";
import MenuIcon from "@mui/icons-material/Menu";
import SearchIcon from "@mui/icons-material/Search";
import NotificationsIcon from "@mui/icons-material/Notifications";
import AccountCircleIcon from "@mui/icons-material/AccountCircle";

const SearchBar = () => {
  const [search, setSearch] = useState("");
  const navi = useNavigate();
  const handleSubmitSearch = (e) => {
    e.preventDefault();
    console.log(search); // 검색 창으로 이동해야 함.
    movePath(navi, "search", `?keyword=${search}`);
    setSearch("");
  };
  const handleChangeSearch = (e) => {
    setSearch(e.currentTarget.value);
  };
  <div>
    <form onSubmit={handleSubmitSearch}>
      <input
        onChange={handleChangeSearch}
        type="text"
        maxLength="12"
        className="search"
        value={search}
        placeholder="게시물 제목 검색"
      />
      <button onClick={handleSubmitSearch}>검색</button>
    </form>
  </div>;
};

const Header = ({ handleOpenMenu, isSearch }) => {
  const navi = useNavigate();
  return (
    <div className="mainpage-header">
      <MenuIcon sx={{ color: "white" }} onClick={handleOpenMenu} />
      <SearchIcon
        sx={{ color: "white" }}
        onClick={() => movePath(navi, "/search")}
      />
      <img
        src="assets/headerLogo.svg"
        alt="header-logo"
        onClick={() => movePath(navi, "/")}
      />
      <NotificationsIcon sx={{ color: "white" }} />
      <AccountCircleIcon
        sx={{ color: "white" }}
        onClick={() => movePath(navi, "/profile")}
      />

      {isSearch && <SearchBar />}
    </div>
  );
};
export default Header;

movePath.jsx

const movePath = (navi, path, query) => {
  if (query === undefined) navi(path);
  else navi(path + query);
};

export default movePath;

hooks로 할당받은 함수를 인자로 넘겨주고 모듈 함수 내부에서 콜백함수로 사용하게 되면 의도했던 대로 작동하게 한다.

좋은 웹페이지 즐겨찾기