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('データの取得に失敗しました'); は実行されませんでした。
通常のQueryである場合は、このモックを使ってtoast.error('データの取得に失敗しました');が実行されますが、LazyQueryではcatch節へ行きません。
なぜcatch節に入らなかったのか
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