axios-hooksで楽々非同期処理!reduxにthunkもsagaもいらない?


はじめに

最近暇な時でGraphQLを触っていて、フロントエンドでApollo-clientをはじめて使ったんですが、
すごく使い勝手がいいhooksがあって(多分GraphQL分からなくても伝わるかなぁと):

// Apollo公式より抜粋
import gql from 'graphql-tag';
import { useQuery } from '@apollo/react-hooks';

const GET_DOGS = gql`
  {
    dogs {
      id
      breed
    }
  }
`;

function Dogs({ onDogSelected }) {
  const { loading, error, data } = useQuery(GET_DOGS); // これです

  if (loading) return 'Loading...';
  if (error) return `Error! ${error.message}`;

  return (
    <select name="dog" onChange={onDogSelected}>
      {data.dogs.map(dog => (
        <option key={dog.id} value={dog.breed}>
          {dog.breed}
        </option>
      ))}
    </select>
  );
}

これ凄くないか?
非同期処理を一行で終わらせてるぞ。

そこで考えたんですが、reduxのプロジェクトで使えたらいいなと。
…ん?待てよ。非同期関連のaxiosを使う人が山ほどいて、
同じことを考える人絶対いるんでしょ!?

調べたらいました、めちゃくちゃいました、山ほどいました(※そこまではいないです)。

やったぜ。

axios-hooks

さて、今回紹介するのはその一つ:axios-hooksです
なぜこれを選んだのは単純に見比べてほしい機能が一番揃ってるからです。(自分なりに)
以下の例は公式のものです:

import useAxios from 'axios-hooks'

function App() {
  const [{ data, loading, error }, refetch] = useAxios(
    'https://api.myjson.com/bins/820fc'
  )

  if (loading) return <p>Loading...</p>
  if (error) return <p>Error!</p>

  return (
    <div>
      <button onClick={refetch}>refetch</button>
      <pre>{JSON.stringify(data, null, 2)}</pre>
    </div>
  )
}

公式のCodeSandboxです

useAxios(url|config, options)

引数

  • url | config - configはaxiosのconfig objectです、なので基本的にaxiosで設定できるものであれば使えます。
  • options - 手動実行とキャッシュの設定です
    • manual - デフォルトはfalseです。trueに設定すればdidMountの時は自動実行しない。大体GETはfalseでCUD(Create, Update, Delete)ではtrueで使うはず。
    • useCache ( true ) - デフォルトはtrueです。

Return object

[{ data, loading, error, response }, execute]

  • data - axiosのresponse.dataです。
  • loading - 名前の通りpending中ではtrueです.
  • error - axios error object
  • response - axios response object
  • execute([config[, options]]) - 手動実行用のfunctionです、引数は基本的に前のconfig、optionsと同じです。

もっと完全な例

自分で書いたものですが、注釈は英語になってます、時間ができたら日本語に直したいと思います。
テスト用APIはwww.mocky.io使わせていただきました。
試したいであれば公式のCodeSandboxにコピペすればできると思います。
(ReactDOMのインポートと下のrender部分は残ってください。)

import React from 'react';
import Axios from 'axios';
import useAxios, {configure} from 'axios-hooks'

// Define axios instance, you could use env to split up production & development
// The useAxios provides directly by axios-hooks use the same instance
// If you need more than one axios instance, explain later
const axiosInstance = Axios.create({baseURL: 'https://www.mocky.io/v2'})
configure({axios: axiosInstance})

// You should define your api url somewhere in your project
const api = {
  getMail: { url: () => '/5ed0a6ea3500005d00ff9d7b?mocky-delay=2000ms', method: 'GET'}, 
  putMail: { url: (id) => `/5ed0a49e3500009300ff9d6b/${id}`, method: 'POST'}
}

function App() {
  // The example to get some data
  // Execute once the component did mount
  const [{data = {iam: 'default data'}, loading, error}] = useAxios({
    url: api.getMail.url(),
    method: api.getMail.method, 
    data: {haha: "yes"}
  })
  // The example to CUD data
  // Pass { manual: true } into useAxios, then it won't execute when component did mount
  const [{data: updatedData}, updateData] = useAxios({method: api.putMail.method}, {manual: true})
  return (
    <div >
      {error && <div>error</div>}
      {data && <div>{`${JSON.stringify(data)} ${loading?'loading':''}`}</div>}
      {updateData && <div>{JSON.stringify(updatedData)}</div>}
      <button onClick={() => {
          // You can set the common authorization header like this way
          axiosInstance.defaults.headers.authorization='test101'
          // Example to update data
          updateData({url: api.putMail.url('myid'), data: {thedata: 'you want to put'}, headers: {'another-header': 'test202'}})
            .then((data) => {console.log(data)}) // Use it by the way you want, even with redux store
      }}>
        test
      </button>
    </div>
  );
}

// If you need more than one axios instance, set up with makeUseAxios & export it
// const anotherInstance = Axios.create({baseURL: 'http://some.another.api.url'})
// export const useAnotherAxios = makeUseAxios({axios: anotherInstance})

export default App;

終わりに

正直redux-sagaを使ったことないので、タイトルではクエスチョンマークを付けてます、ごめんなさい。
余談ですが公式では: axios-hooks is heavily inspired by graphql-hooksと記載しています。
同じgraphqlからの発想で作ったみたいで嬉しいです。

CodeSandboxと注釈は時間ができたら直します。