【React Hooks】 APIでデータの再取得を行うフックの実装方法の考察


フロントエンドエンジニアの茶木です。
APIでデータの再取得を行うフックの実装方法を考察しています。

再取得機能のない getApi のフックの実装と使用例

useGetApi

まず、単純に APIでのデータ取得をコンポーネントで扱いやすくするフックの実装が以下です。
引数の getApi には async() => await axios.get("path") が入る想定です。

コンポーネントマウント時だけ、useEffect 内で geApi がコールされてデータの取得が行われます。

import { AxiosResponse } from "axios";

const useGetApi = <Res>(
  getApi: () => Promise<AxiosResponse<Res, undefined>>
): Res | undefined => {
  const [data, setData] = useState<Res>();
  useEffect(() => {
    getApi().then((res) => setData(res.data));
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);
  return data;
};

API

今回は https://open-meteo.com/ 天気予報のAPIから情報を取得してみようと思います。
定義したFORECAST は、APIで取得できるデータの型で current_weather は現在の天候情報です。

import axios from "axios";

interface FORECAST {
  current_weather: {
    weathercode: number;
    temperature: number;
    time: string;
  };
}

export const api = async () =>
  await axios.get<FORECAST>(
    "https://api.open-meteo.com/v1/forecast?latitude=35.6894&longitude=139.6917¤t_weather=true&timezone=Asia/Tokyo"
  );

呼び出し

フックを使い取得した data を加工して表示します。data が undefined のときは取得前です。

export default function Index(): ReactElement | null {
  const data = useGetApi<FORECAST>(api);
  if (!data) {
    return (
      <p>読み込み中...</p>
    );
  }
  return (
    <div>
      <h1>Forecast</h1>
      <h2>{getWeatherLabel(data.current_weather.weathercode)}</h2>
      <p>気温: {data.current_weather.temperature}℃</p>
      <p>
        <small>{data.current_weather.time} 時点の天気</small>
      </p>
    </div>
  );
}

このように、天候、気温と、時間が取得できます。

再取得機能をもたせたフック

reloadCount を使った再取得機能

ふまえて、この useGetApi を改修して再取得機能をつけます。

import { AxiosResponse } from "axios";

export const useReloadableApi = <Res>(
  getApi: () => Promise<AxiosResponse<Res, undefined>>
): {
  data: Res | undefined,
  reload: () => void
} => {
  const [reloadCount, setReloadCount] = useState(0);
  const [data, setData] = useState<T | undefined>(undefined);
  const reload = useCallback(() => setReloadCount((prev) => prev + 1), []);
  useEffect(() => {
    getApi().then((res) => setData(res.data));
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [reloadCount]);
  return { data, reload };
}

機能解説

コンポーネントマウント時には、useEffect 内部の getApi がデータを取得します。また、reloadCount の更新があったときでも再描画の処理が発生し同様にデータを取得します。reloadCount の更新は、reload メソッドによって起きるので、この reload メソッドを button の onClick などにつけて呼び出せば、再度 APIをリクエストして情報を取得しなおします。

useStateを使った再描画のリクエスト

useState を使ったコンポーネントの再描画(ここでの命名は reloadCount, setReloadCount )は、要するに state の変更を意図的に起こして再描画させる手法で、おそらく一般的なものだと思います。

別解: reloadCount を使わない方法

export const useReloadableApi = <Res>(
  getApi: () => Promise<AxiosResponse<Res, undefined>>
): {
  data: Res | undefined,
  reload: () => void
} => {
  const [data, setData] = useState<Res>();
  const reload = useCallback(
    () => getApi().then((res) => setData(res.data)),
    [getApi]
  );
  useEffect(() => {
    reload();
  }, [reload]);
  return [data, reload];
};

ただ、APIのデータ再取得と再描画に関しては、useState を使わず、reload 内に直接 APIを組み入れてしまう方がより良い方法だと考えています。

reloadCount を使わない方が良いと考える理由は2つです

  • 再描画を起こすためだけの setState の使用は可読性を下げる
  • reload のコール時に、reloadCount を使う方が1回再描画が多い
    • reload コールのタイミングで reloadCount の更新時に1度
    • APIの取得完了タイミングで data の更新時に1度 再描画が発生する

特に後者については、1度目の再描画の data は、更新前の data と同一のため、この data を使用するコンポーネントが適切に実装されていれば、普通は問題は起きないのですが、それでも上位コンポーネントでコントロールできるものについては、コントロールしていくほうが良いと考えます。

おわりに

この他にも、取得したデータをもとにユーザーが変更し、その変更を一時的に保持する必要があるといったケースなど API 関連のフックはバリエーションに富んでいます。ほかにもこのようなフックを紹介していきます。

Gaji-Labo は Next.js, React, TypeScript 開発の実績と知見があります

フロントエンド開発の専門家である私たちが御社の開発チームに入ることで、バックエンドも含めた全体の開発効率が上がります。

「既存のサイトを Next.js に移行したい」
「人手が足りず信頼できるエンジニアを探している」
「自分たちで手を付けてみたがいまいち上手くいかない」

フロントエンド開発に関わるお困りごとがあれば、まずは一度お気軽に Gaji-Labo にご相談ください。

オンラインでのヒアリングとフルリモートでのプロセス支援にも対応しています。

Next.js, React, TypeScript の相談をする!

投稿者 Chaki Hironori

webライターもやってるフロントエンドエンジニアです。Reactは自信があります。またデザイン畑の出身で、気持ちのいいアニメーションやインタラクティブな表現は丁寧に手掛けます。好きなものは中南米の遺跡で、スペイン語が少しできます。