useQueryをクリックイベントで使用する方法

Nextjs（やReactjs）で開発している中で、React Query の useQuery を使うと結果をキャッシュ出来て便利です。

ただuseQueryをonClick時などに使いたい場合、コンポーネントの中の関数内で実行することになります。hooksのルールに反しているのでエラーが発生してしまいます。

## Hooksのルール

Hooksにはルール（[Breaking the Rules of Hooks](https://reactjs.org/warnings/invalid-hook-call-warning.html#breaking-the-rules-of-hooks)）があります。

紹介されているGoodケース

```tsx
function Counter() {
  // ✅ Good: top-level in a function component
  const [count, setCount] = useState(0);
  // ...
}

function useWindowWidth() {
  // ✅ Good: top-level in a custom Hook
  const [width, setWidth] = useState(window.innerWidth);
  // ...
}
```

紹介されているBadケース

```tsx
function Bad1() {
  function handleClick() {
    // 🔴 Bad: inside an event handler (to fix, move it outside!)
    const theme = useContext(ThemeContext);
  }
  // ...
}

function Bad2() {
  const style = useMemo(() => {
    // 🔴 Bad: inside useMemo (to fix, move it outside!)
    const theme = useContext(ThemeContext);
    return createStyle(theme);
  });
  // ...
}

class Bad3 extends React.Component {
  render() {
    // 🔴 Bad: inside a class component
    useEffect(() => {})
    // ...
  }
}
```

## ダメなケース

ルールに反するので、関数コンポーネントのトップレベルで使用せずに、イベントハンドラーで実行するとエラーになります。

```tsx
// イベントハンドラー内で読んでしまっているBadケース
import { FC } from "react";
import { useQuery } from "react-query";

const SampleComponent: FC = () => {
  const handleClick = async () => {
    const { data } = useQuery(
      ["sample-query-key"],
      async () => await fetch("/api/sample-fetch").then((res) => res.json()),
      {
        enabled: false,
      }
    );
  };

  return (
    <div>
      <h1>Favorite MANGA</h1>
      <button onClick={handleClick}>Show Manga</button>
      {/* {data && (
        <>
          <h1>{data.title}</h1>
          <div>
            <img src={data && data.imgSrc} width="250px" height="250px" />
          </div>
        </>
      )} */}
    </div>
  );
};

export default SampleComponent;
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2706663/adc4bed0-093c-6c4f-20fe-ecbad4f78d27.png)
[https://reactjs.org/warnings/invalid-hook-call-warning.html](https://reactjs.org/warnings/invalid-hook-call-warning.html)


とはいえ、トップレベルに記載すると、初回レンダリング時に実行されるので、クリックイベントと連動させることができません。

```tsx
// 通常の書き方ではクリックイベントと連動しない
import { FC } from "react";
import { useQuery } from "react-query";

const SampleComponent: FC = () => {
  const { data } = useQuery(
    ["sample-query-key"],
    async () => await fetch("/api/sample-fetch").then((res) => res.json())
  );

  const handleClick = async () => {};

  return (
    <div>
      <h1>Favorite MANGA</h1>
      <button onClick={handleClick}>Show Manga</button>
      {data && (
        <>
          <h1>{data.title}</h1>
          <div>
            <img src={data && data.imgSrc} width="250px" height="250px" />
          </div>
        </>
      )}
    </div>
  );
};

export default SampleComponent;
```

## イベントと連動させて再フェッチする方法

useQueryにはrefetchがあり、任意のタイミングでフェッチさせることができます。refetch関数をイベントハンドラー内で実行すればOKです。

クリックイベントに連動させる場合は下記のような感じです。

```tsx
import { FC } from "react";
import { useQuery } from "react-query";

const SampleComponent: FC = () => {
  const { data, refetch } = useQuery(
    ["sample-query-key"],
    async () => await fetch("/api/sample-fetch").then((res) => res.json()),
    {
      enabled: false,
    }
  );

  const handleClick = async () => {
    refetch();
  };

  return (
    <div>
      <h1>Favorite MANGA</h1>
      <button onClick={handleClick}>Show Manga</button>
      {data && (
        <>
          <h1>{data.title}</h1>
          <div>
            <img src={data && data.imgSrc} width="250px" height="250px" />
          </div>
        </>
      )}
    </div>
  );
};

export default SampleComponent;
```

まずuseQueryの返り値からrefetchを取り出し、handleClick関数で実行しています。

ポイントは useQuery の第３引数で設定している `enabled: false` になります。

これはつけなくてもいいのですが、falseにしない場合は、初回レンダリング時に自動的にfetchされてしまいます。あくまでボタンなどのイベントが発生したときにのみfetchしたい場合は false にしておきます。

![usequery-refetch (2).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2706663/89edfdd4-e03a-09f8-f06a-2e78be9345e4.gif)


## 参考

- [https://react-query.tanstack.com/reference/useQuery](https://react-query.tanstack.com/reference/useQuery)
- [https://stackoverflow.com/questions/62340697/react-query-how-to-usequery-when-button-is-clicked](https://stackoverflow.com/questions/62340697/react-query-how-to-usequery-when-button-is-clicked)
- [https://qiita.com/suzuki0430/items/fbcd0ce8ee10f91e3d92](https://qiita.com/suzuki0430/items/fbcd0ce8ee10f91e3d92)