🫥

React + fetch + AbortController の最小構成

に公開

Reactコンポーネントにfetch機能を実装したい。かつ、任意のタイミングでキャンセルできるようにしたい。
…を実現するためのサンプルコードになります。

(正直、何かしらのfetchライブラリを使えば、もっと簡単に実現できる気がしますが。。)

ソースコード

Sample.tsx
import { useCallback, useEffect, useRef, useState } from 'react';

export const Sample = () => {
  const [data, setData] = useState('');
  const [isLoading, setLoading] = useState(false);
  const abortControllerRef = useRef<AbortController | null>(null); // … ①

  /**
   * データフェッチ関数
   */
  const fetchData = useCallback(async (url: string) => {
    if (abortControllerRef.current) {
      abortControllerRef.current.abort(); // … ②
    }

    const controller = new AbortController();
    abortControllerRef.current = controller; // … ③

    setLoading(true);

    await fetch(url, { signal: controller.signal }) // … ④
      .then(async (response) => {
        return await response.json();
      })
      .then((result) => {
        setData(JSON.stringify(result));
      })
      .catch((error) => {
        console.error(error);
      })
      .finally(() => {
        setLoading(false);
      });
  }, []);

  /**
   * データフェッチをキャンセルする関数
   */
  const cancelFetch = useCallback(() => { // … ⑤
    if (abortControllerRef.current) {
      abortControllerRef.current.abort();
    }
  }, []);

  /**
   * コンポーネントを破棄する際に、データフェッチを実行中であればキャンセルする
   */
  useEffect(() => {
    return () => {
      if (abortControllerRef.current) {
        abortControllerRef.current.abort();
      }
    };
  }, []);

  return (
    <>
      <div>{isLoading ? 'Loading…' : data}</div>
      <button
        type="button"
        onClick={() => {
          fetchData('https://jsonplaceholder.typicode.com/todos/1');
        }}
      >
        fetch data
      </button>
      <button type="button" onClick={cancelFetch}>
        cancel fetch
      </button>
    </>
  );
};

解説

ソースコードを見ての通り、ではありますが、少しだけ解説します。

  • ①:AbortControllerを入れるための枠をuseRefで用意します。
  • ②:データフェッチ関数fetchDataの中身を見ていきます。
    • まず②のところで、直前に実行していたfetchがあれば、キャンセルします。これで常に1本だけのfetchが実行できるようになりますね。
  • ③:abortControllerRefに、新規のAbortControllerを入れ直します。新しく実行するfetch用のコントローラーになりますね。
  • ④:fetchのパラメータのsignalcontroller.signalを設定します。このあとは通常通り、fetchが実行されていきます。
  • ⑤:fetchが実行中に、データフェッチをキャンセルする関数cancelFetchを任意のタイミングで呼び出せば、fetchをキャンセルできます。
    • データフェッチをキャンセルする関数cancelFetchの中身は、単にabort()を実行しているだけですね。

まとめ

以上、React + fetch + AbortController の最小構成でした。
fetch系ライブラリを使えば、一発で実装できる機能かと思いますが、ライブラリを使わずにライトに作りたい時に重宝しそうです。

Discussion