ReactアプリケーションでAPI通信をシンプルに管理するカスタムフック:usePromiseの解説と活用方法

目次

導入・背景

 ReactアプリケーションでAPI通信を行う際、useEffectを使って非同期処理を記述することが一般的です。しかし、毎回useEffectの中にfetchaxiosを利用してAPI通信を記述すると、コードが冗長になり、コンポーネントの可読性が低下します。

そこで、API通信処理を効率的に管理するためにカスタムフック usePromise を作成し、データ取得の状態(loadingdataerror)をシンプルに扱えるようにしました。本記事では、このusePromiseの実装方法、利用ケース、テストの書き方まで詳しく解説します。

関連パッケージインストール

usePromiseを実装するにあたり、必要なパッケージは以下の通りです。基本的にReactとTypeScriptのみで動作します。

必須パッケージ

  • React: react および react-dom
  • TypeScript: 型定義を追加するため

以下のコマンドでセットアップできます。

npm install react react-dom
npm install --save-dev typescript @types/react @types/react-dom

実行コード

import { useEffect, useState } from "react";

type FetchState<T> = {
  data: T | null;
  loading: boolean;
  error: Error | null;
};

function usePromise<T>(
  requestCommand: (...args: any[]) => Promise<T>,
  args?: object
): FetchState<T> {
  const [state, setState] = useState<FetchState<T>>({
    data: null,
    loading: true,
    error: null,
  });

  useEffect(() => {
    let isCancelled = false;

    const fetchData = async () => {
      setState({ data: null, loading: true, error: null });
      try {
        const response = await requestCommand(args);
        if (!isCancelled) {
          setState({ data: response, loading: false, error: null });
        }
      } catch (err) {
        if (!isCancelled) {
          setState({ data: null, loading: false, error: err as Error });
        }
      }
    };

    fetchData();

    return () => {
      isCancelled = true;
    };
  }, [requestCommand, args]);

  return state;
}

export default usePromise;

Jestを使ったテストコード

usePromiseのテストにはJestReact Testing Libraryを使用します。

パッケージのインストール

npm install --save-dev jest @testing-library/react @testing-library/jest-dom

テストコード:usePromise.test.tsx

import { renderHook } from "@testing-library/react";
import usePromise from "./usePromise";

describe("usePromise", () => {
  const mockApi = jest.fn();

  it("should return data when the request is successful", async () => {
    mockApi.mockResolvedValue({ message: "Success" });

    const { result, waitForNextUpdate } = renderHook(() =>
      usePromise(mockApi)
    );

    expect(result.current.loading).toBeTruthy();

    await waitForNextUpdate();

    expect(result.current.loading).toBeFalsy();
    expect(result.current.data).toEqual({ message: "Success" });
    expect(result.current.error).toBeNull();
  });

  it("should return error when the request fails", async () => {
    mockApi.mockRejectedValue(new Error("Fetch failed"));

    const { result, waitForNextUpdate } = renderHook(() =>
      usePromise(mockApi)
    );

    expect(result.current.loading).toBeTruthy();

    await waitForNextUpdate();

    expect(result.current.loading).toBeFalsy();
    expect(result.current.error).toEqual(new Error("Fetch failed"));
    expect(result.current.data).toBeNull();
  });
});

利用ケース

1. API通信を行う場合

usePromiseを使えば、シンプルにAPIリクエストとその状態管理ができます。

import usePromise from "./usePromise";
import axios from "axios";

const fetchUserData = () => axios.get("https://jsonplaceholder.typicode.com/users").then(res => res.data);

const UserComponent = () => {
  const { data, loading, error } = usePromise(fetchUserData);

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return (
    <div>
      <h1>User Data</h1>
      {data.map((user: any) => (
        <div key={user.id}>{user.name}</div>
      ))}
    </div>
  );
};

export default UserComponent;

2. 引数を使ってAPI通信をする場合

API関数に引数を渡す場合にも対応しています。

const fetchUserById = (params: { id: number }) =>
  axios.get(`https://jsonplaceholder.typicode.com/users/${params.id}`).then(res => res.data);

const UserDetail = ({ userId }: { userId: number }) => {
  const { data, loading, error } = usePromise(fetchUserById, { id: userId });

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return (
    <div>
      <h1>User Detail</h1>
      <p>Name: {data.name}</p>
    </div>
  );
};

まとめ

usePromiseを使うことで、API通信におけるデータの取得・エラーハンドリング・ローディング状態の管理をシンプルに行うことができます。これにより、Reactコンポーネントの責務を減らし、可読性・保守性が向上します。

本記事のポイント

  1. usePromiseの実装方法
  2. Jestを使ったテストコード
  3. API通信の利用ケース

このカスタムフックはどのReactプロジェクトにも容易に統合でき、DRY(Don’t Repeat Yourself)の原則を守りつつ、コードを効率化します。

参考資料

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

新卒4年目です。spring boot, jquery, vue を使ってフロントエンド開発、quarkus、azure kubernetesを使ってバックエンドを作ってました。 今は、UXデザイナーを目指して勉強中です! よろしくお願いします。

コメント

コメントする

CAPTCHA


目次