はじめに
apiのfetch中はローディングのアイコンを出す、ということをやりたい時のtipsです。
コードはあくまで簡易的なものなので、雰囲気を捉えていただければ。。
方法
redux sagaを使っている場合、sagaからaxiosやfetchなどを使ってapiのリクエストを送ると思いますが、以下のような感じのリクエストのラッパーを作って、それを用いてapiを叩くようにします。
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を定義します。
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の時はローディングアイコンを表示するようにします。
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します。
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の状態で表示/非表示をするようにします。ルートのコンポーネント配下に加えることで、あらゆるページでローディングアイコンが表示されます。
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);
ローディングのコンポーネントはあらゆるページの上に被さって表示されるような感じにします。
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;
.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%