axios は HTTP client ライブラリで外部との通信を非同期で行います。
axios/axios: Promise based HTTP client for the browser and node.js
axios を使った時、通信の開始から終了までローディングを示す何かしらを表示することが多いです。これをメソッドチェーンで素朴に記述すると次の様になります。
// チェーンで記述すると
setIsLoading(true);
axios.get('hoge').then((response) => {
// 成功時処理
}).catch(error) => {
// 失敗時処理
}).finally(()=>setIsLoading(false));
// もし isLoading をコンポーネントの state として扱う && 成功時か失敗時のどちらか片方の場合のみ画面遷移する ならば
// finally に setIsLoading(false) は書くべきでないです。React にメモリリークの危険があると警告されます。
ローディング終了処理の記述忘れが起きそうです(何度かやらかしました)。setIsLoading()を無用にすることでこの問題を解決します(他の解としては async/await で setIsLoading(true); await 通信処理(); setIsLoading(false); の定型を記述する方法も有力です)。
React のカスタムフックはコンポーネントに関わらないロジックのみを抽出しつつ React の他のフックを使う関数です。
独自フックの作成 – React
ある関数がフックであるか否かは関数名が use で始まるか否かで定まります。
axios の通信状態に関わる処理をフック内でまとめることで setIsLoading をコンポーネントから消します。具体的には次です。
import { useState } from 'react';
// あらかじめ他で axios.create(独自設定) して作られた axiosInstance
import { BaseRepository } from '@/repository/BaseRepository';
import { AxiosError, AxiosInstance, AxiosResponse } from 'axios';
/**
* axios を使うカスタムフック. 引数で成功時処理、失敗時処理を記述することも可能
*/
export function useAxios(
successHookCallback?: (response: AxiosResponse) => void,
failedHookCallback?: (error: AxiosError) => void
): { isLoading: boolean; axiosInstance: AxiosInstance } {
const axiosInstance = BaseRepository;
// ローディングを state 化
const [isLoading, setIsLoading] = useState(false);
// リクエストが投げられた時にローディングを true に変更
axiosInstance.interceptors.request.use((request) => {
setIsLoading(true);
return request;
});
// レスポンスを受け取った時にローディングを false に変更
axiosInstance.interceptors.response.use(
(response) => {
setIsLoading(false);
typeof successHookCallback === 'function' && successHookCallback(response);
return response;
},
(error) => {
setIsLoading(false);
typeof failedHookCallback === 'function' && failedHookCallback(error);
throw error;
}
);
// 外部にはローディング状態とaxiosInstanceだけ見せる
return {
isLoading,
axiosInstance,
};
}
BaseRepository は以前書いた記事にある様に生成された様々な設定を付与した axios のインスタンスです。
【JavaScript】axiosでAPIに関する常の認証や例外を一か所で定義 – 株式会社シーポイントラボ | 浜松のシステム・RTK-GNSS開発
こうすると次の様に useAxios するだけで必要な情報のみをコンポーネントで扱えます。
// pushMessage は画面共通通知用関数です
// @see https://cpoint-lab.co.jp/article/202008/16339/【React】コンテクストを使った props を用いない関数一つで呼び出せる通知用コンポーネント例 – 株式会社シーポイントラボ | 浜松のシステム・RTK-GNSS開発
const { isLoading, axiosInstance } = useAxios(
() => pushMessage('保存しました。'),
() => pushMessage('保存に失敗しました。')
);
const store = () => {
axiosInstance
.post('user/post', {/** 必要な情報 */})
.then((response) => {
// 通信成功固有処理
});
};
return (
<Button style={{ height: 'auto' }} onClick={store}>
{isLoading ? <LoadingSpinner message={'保存中'} /> : '保存'}
</Button>
)
こんな感じでロジックをフックに抜き出していくとコンポーネントには画面表示に必要な情報のみが残り、いい感じにコードが少なく済み、整理もされます。