Kitsune Blog

よく使うuseFetchのメモ書き

toB向け業務アプリの開発では、クライアントサイドで認証チェックを行ってから、その後データフェッチ処理を行う実装になることが多いと思います。

管理画面系の開発では、ほぼこのパターンな認識です。

その時にフェッチ処理を共通化して、各ページにuseEffectとloadingのstate処理を書かないですむようにしたいという考えから、useFetchというカスタムフックをよく作成しています。

サンプルコードは下記です。

import { useRouter } from "next/router";
import { DependencyList, useEffect, useRef, useState } from "react";

import { useAuth } from "@/context/AuthContext";

export const useFetch = <T>(
  asyncFunc: () => Promise<T>,
  dependencies?: DependencyList
) => {
  const router = useRouter();
  const { account } = useAuth();
  const [isLoading, setIsLoading] = useState<boolean>(true);
  const [isError, setIsError] = useState<boolean>(false);
  const [data, setData] = useState<T | null>(null);
  const originalRef = useRef<T | null>(null);

  useEffect(() => {
    if (!router.isReady) return;
    if (!account) return;
    let unMounted = false;
    const fetchData = async () => {
      try {
        const data = await asyncFunc();
        if (!unMounted) {
          setData(data);
          originalRef.current = data;
          setIsLoading(false);
        }
      } catch (error) {
        console.log(error);
        if (!unMounted) {
          setIsError(true);
          setIsLoading(false);
        }
      }
    };
    fetchData();

    return () => {
      unMounted = true;
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [router.isReady, account, ...(dependencies || [])]);

  const refetch = async () => {
    if (!account) return;
    try {
      setIsLoading(true);
      const data = await asyncFunc();
      setData(data);
      originalRef.current = data;
      setIsLoading(false);
    } catch (error) {
      console.log(error);
      setIsError(true);
      setIsLoading(false);
    }
  };

  return { data, setData, isLoading, isError, originalRef, refetch };
};

ログアウト状態でもフェッチ処理を行う必要がある場合は、引数にboolean型のrequiresLoginなどという項目を付け足して、認証状態とフェッチ処理発動条件を管理します。

フェッチ処理発動条件に細かい制御が必要な場合は引数にbooleanの配列型のconditionsなどという項目を付け足して、制御を行います。

呼び出し方は下記のようになりシンプルです。取得したいデータ型をTypeScriptのGenericsで定義する必要があります。

const { data, setData, isLoading, isError, originalRef, refetch } = useFetch<
  Item[]
>(() => fetchItems());

全ページにuseEffectを書きたくなく、swrなどのデータフェッチライブラリを使用するまでもなく、なおかつ自分好みにカスタマイズしたフェッチ処理を行いたい時に非常に便利です。