★前回の記事
Material-UIのSnackbarを使用して、メッセージを表示します。Snackbarは、画面の端から出てきて1行程度のお知らせを表示するエリアのことです。
未ログイン時の更新エラー(NOT_AUTHENTICATED_ON_TODO_ACTION
)と通信エラー(ADD_TODO_ERROR
とTOGGLE_TODO_ERROR
)の時にSnackbarにエラーメッセージを表示します。データの送信中と送信完了メッセージについては、メッセージの表示をやめます。(次ページでアイコンによる表示を行います。)
Noticeコンポーネントの移動
Noticeコンポーネントをtodosディレクトリから1つ上のcomponentsディレクトリに移動し、todosに限らないお知らせの表示の役割を持たせます。表示する場所もAppコンポーネントに移動します。
src/components/todos/Notice.js
をsrc/components/Notice.js
にリネームします。
src/components/Notice.js
const Notice = ({ notice }) => { // 変更 // 略 } Notice.propTypes = { // 変更 notice: PropTypes.string } export default connect( // 変更 mapStateToProps )(Notice) // 変更
src/components/todos/index.js
// import Notice from './Notice' を削除 class TodoComponent extends React.Component { // 略 render() { const {isOwnTodos, match: { params: {uid}}, classes} = this.props; return ( <div className={classes.root}> <div className={classes.todoListRoot}> <Paper className={classes.todoListContent}> <Title isOwnTodos={isOwnTodos} uid={uid} /> {isOwnTodos && <AddTodo uid={uid} />} {/* <Notice /> を削除 */} <VisibleTodoList uid={uid} isOwnTodos={isOwnTodos} /> </Paper> </div> <FilterNav /> </div> ) } }
src/components/App.js
import Notice from './Notice' // 追加 const App = ({classes}) => ( <BrowserRouter> <div> <CssBaseline /> <Header /> <div className={classes.toolbar} /> <Switch> <Route exact path="/" component={Dashboard} /> <Route exact path="/users/:uid/todos" component={TodoComponent} /> <Route component={NoMatch} /> </Switch> <Notice /> {/* 追加 */} </div> </BrowserRouter> )
Actionの作成
Snackbarが閉じられる時に呼ばれるactionを定義します。
src/actions/index.js
// notice export const CLOSE_NOTICE = 'CLOSE_NOTICE' // 追加
src/actions/noticeActions.js
(新規作成)
import {CLOSE_NOTICE} from './' export const closeNotice = () => ({ type: CLOSE_NOTICE })
Reducerの作成
新規にnotice reducerを作成します。todo reducerは削除します。
src/reducers/notice.js
(新規作成)
import { NOT_AUTHENTICATED_ON_TODO_ACTION, ADD_TODO_ERROR, TOGGLE_TODO_ERROR, LOCATION_CHANGE_ON_TODOS, LOGOUT_SUCCESS, CLOSE_NOTICE} from '../actions/' const INITIAL_STATE = { text: '', level: 'info', open: false } // #1 const notice = (state = INITIAL_STATE, action) => { switch (action.type) { case NOT_AUTHENTICATED_ON_TODO_ACTION : return { text: 'タスクを追加・変更するにはログインしてください', level: 'warning', open: true } // #2 case ADD_TODO_ERROR : case TOGGLE_TODO_ERROR : return { text: 'エラーが発生しました。時間をおいて再度お試しください。', level: 'error', open: true } // #3 case CLOSE_NOTICE : return { ...state, open: false } // #4 case LOCATION_CHANGE_ON_TODOS : case LOGOUT_SUCCESS : return { ...state, open: false } // #5 default: return state } } export default notice
- noticeのstateはtextとlevel、openの3つのプロパティを持ちます。textはSnackbarに表示する文字列、levelは{‘success', 'warning', 'error', 'info’}のいずれかで、Snackbarに表示されるアイコンと背景色をコントロールします。openはSnackbarが表示されているかどうかを示す真偽値です。初期値はそれぞれ空文字、 'info’、falseです(#1)。
- 未ログイン時にタスクの追加・更新を行った場合はwarningレベルのSnackbarにします。openをtrueにしてSnackbarを表示します(#2)。
- タスクの追加・更新時のエラーの時はerrorレベルにしてSnackbarを表示します(#3)。
- CLOSE_NOTICE actionの時にopenをfalseにしてSnackbarを閉じます(#4)。
- ページ遷移の際やログアウトの際にはSnackbarを閉じます(#5)。
src/reducers/todos.js
を削除します。
src/reducers/index.js
// import todos from './todos' を削除 import notice from './notice' // 追加 export default combineReducers({ firebase: firebaseReducer, auth, // todos, を削除 notice, // 追加 visibilityFilter })
レベルごとに表示を出し分ける
レベル('success', 'warning', 'error', 'info’の4つ)ごとにSnackbarの色を変えます。 公式ページのCustomized Snackbars を参考に実装を進めます。
先にSnackbarContentのラッパコンポーネントを作成します。
src/components/util/snackbar/LevelSnackbarContentWrapper.js
(新規作成)
import React from 'react' import PropTypes from 'prop-types' import classNames from 'classnames' import { withStyles } from '@material-ui/core/styles' import IconButton from '@material-ui/core/IconButton' import SnackbarContent from '@material-ui/core/SnackbarContent' import CheckCircleIcon from '@material-ui/icons/CheckCircle' import ErrorIcon from '@material-ui/icons/Error' import InfoIcon from '@material-ui/icons/Info' import CloseIcon from '@material-ui/icons/Close' import WarningIcon from '@material-ui/icons/Warning' import green from '@material-ui/core/colors/green' import amber from '@material-ui/core/colors/amber' const variantIcon = { success: CheckCircleIcon, warning: WarningIcon, error: ErrorIcon, info: InfoIcon, } const styles = theme => ({ success: { backgroundColor: green[600], }, error: { backgroundColor: theme.palette.error.dark, }, info: { backgroundColor: theme.palette.primary.dark, }, warning: { backgroundColor: amber[700], }, icon: { fontSize: 20, }, iconVariant: { opacity: 0.9, marginRight: theme.spacing.unit, }, message: { display: 'flex', alignItems: 'center', }, }) const LevelSnackbarContent = ({ classes, className, message, onClose, variant, ...other }) => { const Icon = variantIcon[variant] return ( <SnackbarContent className={classNames(classes[variant], className)} aria-describedby="client-snackbar" message={ <span id="client-snackbar" className={classes.message}> <Icon className={classNames(classes.icon, classes.iconVariant)} /> {message} </span> } action={[ <IconButton key="close" aria-label="Close" color="inherit" className={classes.close} onClick={onClose} > <CloseIcon className={classes.icon} /> </IconButton>, ]} {...other} /> ) } LevelSnackbarContent.propTypes = { classes: PropTypes.object.isRequired, className: PropTypes.string, message: PropTypes.node, onClose: PropTypes.func, variant: PropTypes.oneOf(['success', 'warning', 'error', 'info']).isRequired } export default withStyles(styles)(LevelSnackbarContent)
最後に、お知らせ(Notice)の表示部分を実装します。
src/components/Notice.js
import React from 'react' import { connect } from 'react-redux' import PropTypes from 'prop-types' import Snackbar from '@material-ui/core/Snackbar'; import { closeNotice } from '../actions/noticeActions' import LevelSnackbarContentWrapper from './util/snackbar/LevelSnackbarContentWrapper' const Notice = ({ text, level, open, handleClose }) => ( <Snackbar // #1 anchorOrigin={{ vertical: 'bottom', horizontal: 'center', }} open={open} autoHideDuration={30000} onClose={handleClose} > <LevelSnackbarContentWrapper // #2 onClose={handleClose} variant={level} message={text} /> </Snackbar> ) Notice.propTypes = { text: PropTypes.string.isRequired, level: PropTypes.oneOf(['success', 'warning', 'error', 'info']).isRequired, open: PropTypes.bool.isRequired, handleClose: PropTypes.func.isRequired } const mapStateToProps = state => { return { text: state.notice.text, level: state.notice.level, open: state.notice.open, } } const mapDispatchToProps = (dispatch, {uid}) => ({ handleClose: () => { dispatch(closeNotice()) } }) export default connect( mapStateToProps, mapDispatchToProps )(Notice)
- Snackbarの設定をします(#1)。anchorOriginに
{vertical: 'bottom', horizontal: 'center’}
を指定して、中央下にSnackbarを表示します。表示状態(open)はstateか取得します。autoHideDurationはSnackbarの表示時間(ミリ秒)です。今回は30000を指定して30秒経過したら自動的にSnackbarが隠れるようにしました。onCloseではcloseNotice actionがdispatchされるようにしています。 - LevelSnackbarContentWrapperの設定をします(#2)。variantにlevelを設定します。messageにtextを渡します。
実行結果
実行結果です。通常の操作ではエラーが発生しないので、少しソースを変更してエラーを出しています。
参考
★次回の記事
★目次