引き続きReact + Firebaseでミニアプリを作っていきます。
前回はFirebase REST APIを使ってCRUDを実装するところまで進めました。
-

React + Firebaseのミニアプリを作る【実装編5】
前回に続きReact+Firebaseでミニアプリを作っていきます。前回の投稿はこちらから。 どんなアプリを作るかは【計画編】を見て下さい。 前回まででCreate, Read, Upd ...
続きを見る
今回はスピナー(ローディング画面)の実装と、通信の成功・失敗のメッセージの表示が出来るようにしていきます。
過去にもスピナーとメッセージは記事に取り上げました。
-

React+axios+Material UIでスピナーとメッセージを表示する
今回はReact + Material UIで通信中にスピナーを表示し、通信終了時にメッセージを表示するプログラムを作成します。非同期通信にはaxiosを利用します。 こちらの記事でRe ...
続きを見る
今回は状態管理にReduxを使用していますが、大きな違いはありません。
ソースコード
今回前提とするコードはこちらから。前回までのCRUDを実装したコードです。
(zipでダウンロードして、yarn installでセットアップして下さい。)
今回の完成コードはこちらから↓
フォルダ構成やファイルの全体像がわからなくなった場合は完成コードを参照して下さい。
スピナー(ローディング画面)の表示
コンポーネント
このアプリではMaterial UIを使っているので、コンポーネントの実装はほぼ必要ありません。
Material UIのBackdropを使うだけです。
まず以下のようなBackdropコンポーネントを用意します。
components > utils > Backdrop.js
import React from "react";
import CircularProgress from "@material-ui/core/CircularProgress";
import Backdrop from "@material-ui/core/Backdrop";
const CustomBackdrop = props => {
const { showBackdrop } = props;
return (
<Backdrop open={showBackdrop}>
<CircularProgress color="inherit" />
</Backdrop>
);
};
export default CustomBackdrop;
ロジックは簡単で、処理中はshowBackdropをtrueにし、通常表示されるコンポーネントに変わってBackdropを表示させます。
リスト画面ではデータをAPI通信で読み込むため、その間Backdropを表示させます。
components > content > ListView.jsに以下のコードを追加します。
// 略
import Backdrop from "../utils/Backdrop";
const ListView = props => {
// 略
const showBackdrop = useSelector(state => state.uiState.showBackdrop);
// ここからBackdropに関係する処理
let list;
if (showBackdrop) {
// showBackdrop=trueのときはコンテンツをBackdropで置き換える
list = <Backdrop showBackdrop={showBackdrop} />;
} else {
list = items.map(item => (
<Grid item key={item.id}>
<Link to={`/detail/${item.id}`} className={classes.link}>
<MediaCard title={item.title} />
</Link>
</Grid>
));
}
return (
<React.Fragment>
<Grid item xs={12}>
<Box textAlign="center">
<Link to="/create" className={classes.link}>
<Button variant="contained" color="primary">
Create
</Button>
</Link>
</Box>
</Grid>
{list}
</React.Fragment>
);
};
今回はBackdropの表示状況はReduxで管理するため、useSelectorで表示状態を取得しています。
詳細画面でも同様で通信の際には表示するコンポーネントをBackdropに置換します。
components > content > DetailView.jsに以下のコードを追加します。
// 略
import Backdrop from "../utils/Backdrop";
const DetailView = props => {
// 略
const showBackdrop = useSelector(state => state.uiState.showBackdrop);
let detail;
if (showBackdrop) {
detail = <Backdrop showBackdrop={showBackdrop} />;
} else {
detail = (
ここに元々あったコンポーネント
);
}
return <React.Fragment>{detail}</React.Fragment>;
};
Reducer
actions > actionTypes.jsに以下のアクションを追加します。
// UI State export const SHOW_BACKDROP = "SHOW_BACKDROP"; export const HIDE_BACKDROP = "HIDE_BACKDROP";
reducerは極めてシンプルです。アクションによってshowBackdropの値をtrue/falseに変更するだけです。
reducers > uiState.js
import * as ACTION_TYPES from "../actions/actionTypes";
const initialState = {
showBackdrop: false,
};
const uiState = (state = initialState, action) => {
switch (action.type) {
case ACTION_TYPES.SHOW_BACKDROP:
return {
...state,
showBackdrop: true
};
case ACTION_TYPES.HIDE_BACKDROP:
return {
...state,
showBackdrop: false
};
default:
return state;
}
};
export default uiState;
作成したuiStateをcombineReducersに渡すことを忘れずに!
reducers > index.js
import { combineReducers } from "redux";
import subjects from "./subjects";
import uiState from "./uiState";
const rootReducer = combineReducers({ subjects, uiState });
export default rootReducer;
Action Creator
まずはBackdropに関するaction creatorを作成します。
内容はactionを作成するだけです。
actions > uiState.jsを新たに作成します。
import * as ACTION_TYPES from "../actions/actionTypes";
export const showBackdrop = () => {
return {
type: ACTION_TYPES.SHOW_BACKDROP
};
};
export const hideBackdrop = () => {
return {
type: ACTION_TYPES.HIDE_BACKDROP
};
};
後は各action creatorの最初にshowBackdropをdispatchし、終了時にhideBackdropをdispatchします。
以下は一例です。
export const readSubject = () => async dispatch => {
dispatch(showBackdrop()); // Backdropを表示
try {
const result = await axios.get("/subject.json");
const subjectList = Object.keys(result.data).map(id => ({
...result.data[id],
id: id
}));
dispatch(readSubjectSuccess(subjectList));
dispatch(hideBackdrop()); // Backdropを非表示に
} catch (error) {
dispatch(apiFailed("read subject failed"));
}
};ポイント
処理後にページ遷移などがある場合は、hideBackdropは行わなくていいこともあります。
エラー時のhideBackdropは忘れがちなので注意。
export const apiFailed = message => dispatch => {
dispatch(hideBackdrop());
return {
type: ACTION_TYPES.API_FAILED,
payload: {
message: message
}
};
};
このようにローディング画面が表示されます。

メッセージの表示
次にユーザが操作を行った時に成功・失敗のメッセージを表示させるようにします。
コンポーネント
Material UIのSnackbarというコンポーネントを利用します。
こんな感じのものを表示したい。

このコンポーネントはAlertというコンポーネントを使用しており、追加でmaterial-ui/labのパッケージをインストールする必要があります。
yarn add @material-ui/lab
コードはMaterial UIの例をほぼそのまま使えばいいのですが、表示の状態・色・メッセージ内容はReduxで管理します。
components > utils > SnackBar.jsを以下のように作成・または編集して下さい。
import React from "react";
import { useDispatch, useSelector } from "react-redux";
import Snackbar from "@material-ui/core/Snackbar";
import MuiAlert from "@material-ui/lab/Alert";
import { hideMessage } from "../../actions/uiState";
const Alert = props => {
return <MuiAlert elevation={6} variant="filled" {...props} />;
};
const CustomizedSnackbars = props => {
const dispatch = useDispatch();
const open = useSelector(state => state.uiState.showMessage);
const type = useSelector(state => state.uiState.messageType);
const message = useSelector(state => state.uiState.message);
const handleClose = () => {
dispatch(hideMessage());
};
return (
<Snackbar
open={open}
autoHideDuration={3000}
onClose={handleClose}
anchorOrigin={{ vertical: "bottom", horizontal: "left" }}
>
<Alert onClose={handleClose} severity={type}>
{message}
</Alert>
</Snackbar>
);
};
export default CustomizedSnackbars;
メッセージの色を指定するtypeはsuccess・info・warning・errorの4種類のうちのどれかから選ぶ必要があります。
snackbarは自動で非表示になるのですが、非表示になるまでの時間をautoHideDurationで指定できます。
また、anchorOriginで表示する場所を指定できますので好みに応じて変えて下さい。
補足
完成コードではスライド表示されるようにカスタマイズされています。
これを表示させるため、
components > layout > Content.jsにSnackbarを配置しておきます。
const Content = props => {
const classes = useStyles();
return (
<Container>
<Snackbar />
<Switch>
......
このような形で配置しておいて下さい。表示しているコンポーネントの中にSnackbarがあればいいので、場所はあまり関係ありません。
Reducer
まず最初にactionTypes.jsに必要なaction typeを追加します。
「メッセージを表示する」・「非表示にする」の2つを追加します。
export const SHOW_MESSAGE = "SHOW_MESSAGE"; export const HIDE_MESSAGE = "HIDE_MESSAGE";
reducerでメッセージの色(type)、内容、表示状態を管理するよう変更します。
reducers > uiState.jsは以下のようになります。
import * as ACTION_TYPES from "../actions/actionTypes";
const initialState = {
showBackdrop: false,
showMessage: false,
message: "",
messageType: "success"
};
const uiState = (state = initialState, action) => {
switch (action.type) {
case ACTION_TYPES.SHOW_BACKDROP:
return {
...state,
showBackdrop: true
};
case ACTION_TYPES.HIDE_BACKDROP:
return {
...state,
showBackdrop: false
};
case ACTION_TYPES.SHOW_MESSAGE:
return {
...state,
showMessage: true,
message: action.payload.message,
messageType: action.payload.messageType
};
case ACTION_TYPES.HIDE_MESSAGE:
return {
...state,
showMessage: false,
message: "",
messageType: "success"
};
default:
return state;
}
};
export default uiState;
Action Creator
action creatorは極めてシンプルです。表示の際にメッセージの内容と色を指定するだけです。
export const showMessage = (message, type) => {
return {
type: ACTION_TYPES.SHOW_MESSAGE,
payload: { message: message, messageType: type }
};
};
export const hideMessage = () => {
return {
type: ACTION_TYPES.HIDE_MESSAGE
};
};
あとはこれを必要に応じてdispatchします。
例えばcreateSubjectでは以下の通り。
import { showMessage } from "./uiState";
export const createSubject = data => async dispatch => {
const subject = { title: data.title, image: "" };
try {
const result = await axios.post("/subject.json", subject);
dispatch(createSubjectSuccess(data, result.data.name));
dispatch(createInitialQuestion(result.data.name));
dispatch(showMessage("新規作成に成功しました。", "success")); // メッセージを表示
} catch (error) {
dispatch(apiFailed("create subject failed"));
}
};
このようなメッセージが左下に表示されます。

これでメッセージも表示できました。この他にもエラーメッセージなど必要に応じてメッセージを表示するよう追加しておいて下さい。
次回は認証を実装します。

