React axios インターセプターでフックを使えるようにする

32895 ワード

Vite + React + TypeScript 環境で axios を使用しています。
axiosのインタセプターでフックを使おうとしたのですが、その方法がわからずちょっと迷ったので、解決した方法を記事にまとめました。
axiosのバージョン: v0.25.0

axiosで、デフォルトConfig と インタセプター基本的な設定方法

axios公式サイト:デフォルトConfig
axios公式サイト:インタセプター

まずは公式サイトに従って、基本的な方法でデフォルトConfig と インタセプターを作成してみます。
axios 拡張用のファイルを作成します。
ファイル名はなんでもいいですが、axiosClient.ts というファイル名で作成しました。

▶ すべての HTTP リクエストに共通するデフォルト config を指定する

すべての HTTP リクエストに共通するデフォルトの config を指定するには、axios.create メソッドの引数に config 値を指定します。

axiosClient.ts
export const axiosClient  = axios.create({
  baseURL: 'https://xxxxx.xxx.com',
  timeout: 3000,
  headers: {
    'Content-Type': 'application/json'
  }
})

▶ すべてのリクエストに割り込み処理を入れる

先ほど axios.create メソッドで作成した axios インスタンスに、リクエストが送信される前に割り込み処理(インターセプター)を追加します。
リクエストインターセプターではリクエストヘッダーにアクセストークンを設定したりといった用途に使われます。

axiosClient.ts
axiosClient.interceptors.request.use((config: AxiosRequestConfig) => {
  if (config.headers !== undefined) {
    // ヘッダにアクセストークンを埋める
    // const accessToken = getAccessToken()
    // if (accessToken) {
    //   config.headers.Authorization = `Bearer ${accessToken}`
    // }
  }
  return config
})

▶ すべてのレスポンスに割り込み処理を入れる

リクエストのインターセプターと同様、axios.create メソッドで作成した axios インスタンスに、レスポンスへの割り込み処理(インターセプター)を追加します。
ログを残したり、エラーを調整したりといった用途に使われます。

axiosClient.ts
axiosClient.interceptors.response.use(
  (response: AxiosResponse) => {
    return response
  },
  (error: AxiosError) => {
    switch (error.response?.status) {
      case 401:
        // なにかする
        break
      default:
        break
    }
    return Promise.reject(error)
   }
 )

▶ 全部まとめると

axiosClient.ts
import axios, { AxiosError, AxiosRequestConfig, AxiosResponse } from 'axios'

const BaseUrl = 'https://xxxxx.xxx.com'

/**
 * デフォルト config の設定
 */
export const axiosClient  = axios.create({
  baseURL: BaseUrl,
  timeout: 3000,
  headers: {
    'Content-Type': 'application/json'
  }
})


/**
 * リクエスト インターセプター
 */
axiosClient.interceptors.request.use((config: AxiosRequestConfig) => {
  if (config.headers !== undefined) {
    // --ヘッダにアクセストークンを埋める
    // const accessToken = getAccessToken()
    // if (accessToken) {
    //   config.headers.Authorization = `Bearer ${accessToken}`
    // }
  }
  return config
})

/**
 * レスポンス インターセプター
 */
axiosClient.interceptors.response.use(
  (response: AxiosResponse) => {
    return response
  },
  (error: AxiosError) => {
    switch (error.response?.status) {
      case 401:
        // なにかする
        break
      default:
        break
    }
    return Promise.reject(error)
   }
 )

使用するときは、axios.create メソッドで作成した axios インスタンスからリクエストを送信します。

+ import { axiosClient } from '@/_lib/axiosConfig'

export type Sample = {
  id: number
  name: string
}

export function useSampleApi() {

  const GetAll = async (): Promise<Sample[]> => {
    try {
+      const response = await axiosClient.get(`/api/Sample/GetAll`)
      return response.data
    } catch (error: unknown) {
      throw error
    }
  }

}

フックをつかえるようにする

先ほど作成した axiosClient.ts は関数コンポーネントではないので、このままではフックを使えません。
関数コンポーネントに変更して、フックを使えるようにします。
useEffect で インターセプターを設定し、コンポーネントのアンマウント時にインターセプターを解除するクリーンアップ関数を作成します。
2022/05/12 prop を書き忘れていたので追記しました。

AxiosClientProvider.tsx
import React from 'react'
import axios, { AxiosRequestConfig } from 'axios'
import { useNavigate } from 'react-router-dom'

const BaseUrl = 'https://xxxxx.xxx.com'

// デフォルト config の設定
export const axiosClient = axios.create({
  baseURL: BaseUrl,
  timeout: 5000,
  headers: {
    'Content-Type': 'application/json'
  }
})


export function AxiosClientProvider({children}: {children: React.ReactElement}) {
 // 関数コンポーネントなのでフックが使える
+  const navigate = useNavigate() 
  
  React.useEffect(() => {
    
    // リクエスト インターセプター
    const requestInterceptors = axiosClient.interceptors.request.use((config: AxiosRequestConfig) => {
      if (config.headers !== undefined) {
        // const accessToken = getAccessToken()
        // if (accessToken) {
        //   config.headers.Authorization = `Bearer ${accessToken}`
        // }
      }
      return config
    })

    // レスポンス インターセプター
    const responseInterceptor = axiosClient.interceptors.response.use(
      (response) => {
        return response
      },
      (error) => {
        switch (error.response?.status) {
          case 401:
            // なにかする
            break
          default:
            break
        }
        return Promise.reject(error)
       }
     )

    
    // クリーンアップ
    return () => {
      axiosClient.interceptors.request.eject(requestInterceptors)
      axiosClient.interceptors.response.eject(responseInterceptor)
    }

  }, [])
  
  return (<>{children}</>)
}

使い方はApp.tsxなどで、作成した AxiosClientProvider でコンポーネントをラップします。

App.tsx
import React from 'react'
import { ErrorBoundary } from 'react-error-boundary'
import { BrowserRouter } from 'react-router-dom'
import { ThemeProvider, createTheme } from '@mui/material/styles'
import { appTheme } from '@/_styles/appTheme'
import { Global } from '@emotion/react'
import sanitize from '@/assets/sanitize.css' 
import { ErrorFallback } from '@/pages'
import { AxiosClientProvider } from '@/_lib/AxiosClientProvider'
import { SampleComponent } from '@/pages'

const theme = createTheme(appTheme)

export function App() {
  return (
    <React.Suspense fallback={<div>Loading...</div>}>
      <ErrorBoundary FallbackComponent={ErrorFallback}>
        <BrowserRouter>
+          <AxiosClientProvider>
            <ThemeProvider theme={theme}>
              <Global styles={sanitize} />
              <SampleComponent />
            </ThemeProvider>
+          </AxiosClientProvider>
        </BrowserRouter>
      </ErrorBoundary>
    </React.Suspense>
  )
}

リクエストを送信する際は、AxiosClientProvider.tsx で作成した axios インスタンスを使用します。

import { axiosClient } from '@/_lib/AxiosClientProvider'

export type Sample = {
  id: number
  name: string
}

export function useSampleApi() {

  const GetAll = async (): Promise<Sample[]> => {
    try {
      const response = await axiosClient.get<Sample[]>(`/api/Sample/GetAll`)
      return response.data
    } catch (error: unknown) {
      throw error
    }
  }
  
  return { GetAll }
}

以上、axiosインターセプターでフックを使えるようになりました。