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> )
こんな感じでロジックをフックに抜き出していくとコンポーネントには画面表示に必要な情報のみが残り、いい感じにコードが少なく済み、整理もされます。