😺

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

2022/11/26に公開

fetchしたデータを使ってコンポーネントを描画する

fetchしたデータをもとに描画するとき、SWRがとても便利です。

カスタムフックの実装

SWRを使うと、fetchしたデータをキャッシュしてくれます。
また、ブラウザのタブを切り替えたときには自動でデータの再検証をしてくれたり、あるいはmutateを使って任意のタイミングでデータを再検証できます。

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でデータを再検証することができます。
これがあればuseEffectを使ったデータの再検証を代替できますね。

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

export const SomeComponent: React.FC<{}> = ({}) => {
  const [startFetch, setStartFetch] = useState(false);
  const { data, isLoading, refetchData } = useFetchData(startFetch);
  
  return (
    <>
      <Button onClick={() => {setStartFetch(true)}}>フェッチ開始</Button>
      <Button onClick={() => {refetchData()}}>データ再検証</Button>
      {isLoading && <LoadingComponent />}
      {!isLoading && data && <ComponentToShowData data={data}/>}
    </>
  );
};

任意のイベントが起きた時だけデータをfetchしたいとき

たとえば、ボタンを押した時にfetchを開始して何らかの処理をする場合を考えます。
上記のSWRを使ったユースケースと異なるのは、「ボタンを押した時だけ」 APIをcallする、という点です。
SWRはとても便利ですが、fetchしたデータはキャッシュされ、さらにブラウザのタブを切り替えた時などに自動でデータ再検証(APIの再call)が行われてしまいます。
上記が不要なケースでは下記のようにカスタムフックを書くことができます。
(SWRでも自動再検証のキャンセルができるようです)

カスタムフックの実装

useFetchData.ts
import { useCallback, useState } from 'react';

type Data = {
  something: string;
}

export const useFetchData = () => {
  const [dataState, setDataState] = useState<{
    data: Data | 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

Discussion