🚀

Apollo Client v3のuseLazyQueryはエラー時もrejectしない

に公開

こんにちは、ファインディでエンジニアとして働いているそんちょ@sontixyouです。
この記事は、ファインディエンジニア Advent Calendar 2025の15日目の記事です。

今回は、Apollo Client v3のuseLazyQueryのエラーハンドリングの挙動についての記事です。

きっかけ

Apollo Client v3のuseLazyQueryを使って、APIへリクエストする処理を初めて書きました。
そこで、Apollo Clientのエラーハンドリングでハマった箇所をご紹介します。

この記事では、useLazyQueryのPromiseがエラー時もrejectされずresolveされる挙動と、その対処法を解説します。useLazyQueryでcatch節が実行されず困っている方の参考になれば幸いです。

Apollo Clientとは

Apollo Clientは、GraphQLクライアントライブラリで、Reactや他のフレームワークでGraphQL APIとやり取りするために使用されます。

Apollo Clientは、クエリの実行、キャッシュ管理、状態管理など、多くの機能を提供し、開発者が効率的にGraphQLを利用できるようにします。

LazyQueryとは

通常のuseQueryとの違いは次のとおりです。

useQuery useLazyQuery
実行タイミング コンポーネントのマウント時に自動実行 明示的に関数を呼ぶまで実行されない
ユースケース ページ読み込み時にデータ取得 ユーザー操作に応じてデータ取得

useLazyQueryのcatch節が実行されない問題

私は次のようなLazyQueryを使ったコードを書きました。

export const useFetchUserInfoFacade = ({
  userId,
}: {
  userId: number;
}) => {
  const [
    fetchUserInfo,
    {
      data,
      loading,
      error,
    },
  ] = useFetchUserInfoLazyQuery();

  useEffect(() => {
    fetchUserInfo({
      variables: {
        userId,
      },
    }).then((response) => {
      if (response.error) return;
      if (!response.data?.user?.id) {
        toast.error('データが見つかりません');
        return;
      }
    }).catch(() => {
      toast.error('データの取得に失敗しました');
    });
  }, [userId, fetchUserInfo]);

上のコードで、catch節にあるtoast.error('データの取得に失敗しました'); が実行されることを確認するために、単体テストで次のモックを用意しました。

私の予想では、次のモックを使うことでcatch節へ入ることを確認できるはずだと思っていました。

const mocks = [
  {
    request: {
      query: FetchUserInfoDocument,
      variables: {
        userId: 1,
      },
    },
    error: new Error('Network Error'),
  },
];

const { result } = renderHook(
  () =>
    useUserInfoFacade({
       userId: 1,
    }),
    {
      wrapper: (props) => <Wrapper {...props} mocks={mocks} />,
    }
);

しかし、現実は違いました。テストを実行したときに、catch節へは入らず、toast.error('データの取得に失敗しました'); は実行されませんでした。

https://github.com/apollographql/apollo-client/blob/version-3.x/src/react/hooks/useQuery.ts#L453-L487

通常のQueryである場合は、このモックを使ってtoast.error('データの取得に失敗しました');が実行されますが、LazyQueryではcatch節へ行きません。

なぜcatch節に入らなかったのか

https://github.com/apollographql/apollo-client/blob/version-3.x/src/react/hooks/useLazyQuery.ts#L358-L383

error が発生した場合でも、Promiseはrejectされずにresolveされるためです。
そのため、catch節には入らずにthen節に入ります。

これがわかったことで、次のようにthen節でエラーハンドリングを行う必要があることがわかりました。

export const useFetchUserInfoFacade = ({
  userId,
}: {
  userId: number;
}) => {
  const [
    fetchUserInfo,
    {
      data,
      loading,
      error,
    },
  ] = useFetchUserInfoLazyQuery();

  useEffect(() => {
    fetchUserInfo({
      variables: {
        userId,
      },
    }).then((response) => {
      if (response.error) {
        toast.error('データの取得に失敗しました');
        return;
      }

      if (!response.data?.user?.id) {
        toast.error('データが見つかりません');
        return;
      }
    });
  }, [userId, fetchUserInfo]);

まとめ

今回の経験から得られた学びは次のとおりです。

useLazyQueryはエラーが発生してもPromiseをrejectせずresolveします。
そのため、catch節ではなくthen節内でresponse.errorをチェックする必要があります。

useQueryとuseLazyQueryではエラーハンドリングの挙動が異なります。

useLazyQueryを使う際は、catch節でのエラーハンドリングに頼らず、必ずresponse.errorを確認するようにしましょう。

ライブラリの挙動に迷ったときは、公式ドキュメントだけでなくソースコードを読むことで、根本的な理解が得られます。

Discussion