【React入門】データをfetchするときに便利なカスタムフック

# はじめに
コンポーネントからロジックを分離するためにカスタムフックがよく使われますね。
今回はAPIからデータをfetchするカスタムフックの書き方について備忘録的にまとめます。

# fetchしたデータを使ってコンポーネントをrenderしたいとき
fetchしたデータをもとにrenderするとき、`SWR`がとても便利です。

https://swr.vercel.app/ja/docs/getting-started

## カスタムフックの実装
`SWR`を使うと、fetchしたデータをキャッシュしてrenderに使うことができます。
また、ブラウザのタブを切り替えたときには自動でデータの再検証をしてくれたり、あるいは`mutate`をinvokeすることで任意のタイミングでデータを再検証することができます。

```useFetchData.ts
import useSWR, { mutate } from 'swr';
import { useCallback } from 'react';

export const useFetchData = (
  startFetch: boolean,
) => {
  const key = 'some_unique_key';
  const { data, error } = useSWR(startFetch ? key : null, () => 
    axios.get('your_endpoint_here')
  );

  const refetchData = useCallback(() => {
    mutate(key);
  }, [key]);

  return {
    data,
    isLoading: !data && !error,
    isError: !!error,
    refetchData
  };
};
```
## コンポーネントの実装
`startFetch === true`になればfetchが開始されます。
データをfetchしている最中は`isLoading === true`となり、これを使ってロード中の表示ができます。
fetchはもちろん非同期的に行われますが、`{ data, isLoading, refetchData } `ではresolveされた値が返ってくるので、コンポーネント側では非同期を意識する必要はありません。
また、`refetchData`をinvokeすればデータを再検証することができます。
これがあれば`useEffect`を使ったデータの再検証を代替できますね。

```component.tsx
import { useFetchData } from './useFetchData';

export const SomeComponent: React.FC<{}> = ({}) => {
  const [startFetch, setStartFetch] = useState(false);
  const { data, isLoading, refetchData } = useFetchData(startFetch);

  const handleStartFetch = useCallback(() => {
    setStartFetch(true);
  }, []);

  return (
    <>
      <Button onClick={handleStartFetch}>フェッチ開始</Button>
      <Button onClick={refetchData}>データ再検証</Button>
      {isLoading && <LoadingComponent />}
      {!isLoading && data && <ComponentToShowData data={data}/>}
    </>
  );
};
```

# 任意のイベントが起きた時にデータをfetchして処理したいとき
たとえば、ボタンを押した時にfetchを開始して何らかの処理をする場合を考えます。
上記の`SWR`を使ったユースケースと異なるのは、「ボタンを押した時だけ」APIをcallする、という点です。
`SWR`はとても便利ですが、fetchしたデータはキャッシュされ、さらにブラウザのタブを切り替えた時などに自動でデータ再検証（APIの再call）が行われます。
上記が不要なケースでは下記のようにカスタムフックを書くことができます。
[(*SWRでも自動再検証のキャンセルができるようです)](https://swr.vercel.app/ja/docs/revalidation#%E8%87%AA%E5%8B%95%E5%86%8D%E6%A4%9C%E8%A8%BC%E3%81%AE%E7%84%A1%E5%8A%B9%E5%8C%96)

## カスタムフックの実装
```useFetchData.ts
import { useCallback, useState } from 'react';

interface IData {
  something: string;
}

export const useFetchData = () => {
  const [dataState, setDataState] = useState<{
    data: IData | null;
    isLoading: boolean;
  }>({
    data: null,
    isLoading: false
  });

  const fetchData = useCallback(async () => {
    setDataState({
      data: null,
      isLoading: true
    });
    const data = await axios.get('your_endpoint_here')
    setDataState({ data, isLoading: false });
    return data;
  }, []);

  const fetchDataAndDoSomething = useCallback(async () => {
    const data = await fetchData();
    // do something here
  }, []);

  return { ...dataState, fetchDataAndDoSomething };
};
```
## コンポーネントの実装
```component.tsx
import { useFetchData } from './useFetchData';

export const SomeComponent: React.FC<{}> = ({}) => {
  const [startFetch, setStartFetch] = useState(false);
  const { data, isLoading, fetchDataAndDoSomething } = useFetchData();

  return (
    <>
      <Button onClick={fetchDataAndDoSomething}>
        'Fetch data and do something'
        {isLoading && <LoadingComponent />}
      </Button>
    </>
  );
};
```

# 参考にさせていただいたもの

https://blog.gaji.jp/2022/01/28/8981/

https://zenn.dev/shikky0331/articles/96fe7208a6efb57f5b7e

https://streamich.github.io/react-use/?path=/story/side-effects-useasyncfn--docs