redux sagaでローディングのアイコンをどう出すか

# はじめに
apiのfetch中はローディングのアイコンを出す、ということをやりたい時のtipsです。
コードはあくまで簡易的なものなので、雰囲気を捉えていただければ。。


# 方法
redux sagaを使っている場合、sagaからaxiosやfetchなどを使ってapiのリクエストを送ると思いますが、以下のような感じのリクエストのラッパーを作って、それを用いてapiを叩くようにします。

```utils/request.js
import axios from 'axios';

class Request {
  async get(url) {
    const res = await axios.get(url);

    return res;
  }
}

export default Request;

// ↑これをsagaのapi叩くところで使っていく。
// import Request from 'utils/request';
// const request = new Request();
// await request.get('apiのurl')
```


次は、apiのfetch開始、fetch終了のaction、reducerを定義します。

```actions/common.js
export const SHOW_LOADING = Symbol('SHOW_LOADING');
export const HIDE_LOADING = Symbol('HIDE_LOADING');

export const showLoading = () => ({ type: SHOW_LOADING });
export const hideLoading = () => ({ type: HIDE_LOADING });
```

storeのcommon.isLoadingがtrueの時はローディングアイコンを表示するようにします。

```reducer/common.js
import * as commonActions from 'actions/common';

const initialState = {
  isLoading: false,
};

export default (state = initialState, action) => {
  switch (action.type) {
    case commonActions.SHOW_LOADING:
      return { ...state, ...{ isLoading: true } };
    case commonActions.HIDE_LOADING:
      return { ...state, ...{ isLoading: false } };
    default:
      return state;
  }
};
```

先ほど作成したリクエストのラッパー内を修正します。
reduxのstoreに生えているdispatchを利用する形で、SHOW_LOADINGとHIDE_LOADINGのアクションをdispatchします。

```utils/request.js
import axios from 'axios';
import { showLoading, hideLoading } from 'actions/common';
import store from 'store'; // reduxのstore

class Request {
  async get(url) {
    store.dispatch(showLoading());

    const res = await axios.get(url).finally(() => store.dispatch(hideLoading()));

    return res;
  }
}

export default Request;
```

これで、utils/request.jsを利用してGETのapiリクエストを行った際は、common.isLoadingがfalse => true => falseと遷移するようになりました。 


あとは、アプリのルートコンポーネント配下にローディングアイコンのコンポーネントを加え、common.isLoadingの状態で表示/非表示をするようにします。ルートのコンポーネント配下に加えることで、あらゆるページでローディングアイコンが表示されます。

```App.jsx
import React, { Component } from 'react';
import { connect } from 'react-redux';
import Loading from 'component/commons/Loading';

class App extends Component {
  render() {
    const { common } = this.props;

    return (
      <div>
        <Loading isLoading={common.isLoading} />
        <アプリのルートコンポーネント />
      </div>
    );
  }
}

function mapStateToProps(state) {
  return {
    common: state.common,
  };
}

export default connect(mapStateToProps)(App);
```


ローディングのコンポーネントはあらゆるページの上に被さって表示されるような感じにします。

```component/commons/Loading/index.jsx
import React from 'react';
import CircularProgress from '@material-ui/core/CircularProgress';
import style from './style.styl';

const Loading = ({ isLoading }) => (
  <div>
    {isLoading && (
      <div className={style.loadingBox}>
        <div className={style.boxInner}>
          <div className={style.boxBg}>
            <CircularProgress />
          </div>
        </div>
      </div>
    )}
  </div>
);

export default Loading;
```

```component/commons/Loading/style.styl
.loadingBox
  z-index 10
  position fixed
  display table
  width 100%
  height 100%
  background-color rgba(255, 255, 255, .75)

.boxInner
  display flex
  justify-content center
  align-items center
  height 100%
  width 100%

.boxBg
  width 80px
  height 80px

.boxIcon
  width 100%
  height 100%
```