STEP 3での成果物 (STEP 3 : 他のユーザのタスクが見れるタスク管理アプリを作成する - React + Redux + Firebase チュートリアル)
★前回の記事
STEP 3では、Cloud Functions for Firebaseを使用して、タスクの作成/更新があった時に、"最近の更新”に登録します。 ユーザは"最近の更新"から各ユーザのタスク一覧へジャンプすることができます。
タスク一覧のURLは、/users/{ユーザID}/todos
にするため、react-router-domを使用します。また、react-router-domをreduxと一緒に使うため、connected-react-routerを利用します。
自分のタスクのみ編集できるように変更もします。
"最近の更新”は、そのままだとどんどんデータが溜まってしまうので、Cloud Functionsを利用して適宜一定の数以下になるよう、古いデータを削除するようにします。
それでは、STEP 3のチュートリアルを始めましょう。
★次回の記事
★目次
Firebaseアプリの公開 (STEP 2 : ユーザ認証を行なって、自分だけが読み込めて書き込めるタスク管理アプリを作成する - React + Redux + Firebase チュートリアル)
★前回の記事
アプリが完成したので、Firebaseにアプリを公開します。
ビルドします。
$ yarn run build
一旦ローカルで正常に動くか確認しましょう。
$ firebase serve
http://localhost:5000にアクセスして、動作確認しましょう。 確認が終わったら、Firebaseにデプロイします。
$ firebase deploy === Deploying to 'todo-sample-xxxxx’… i deploying database, hosting i database: checking rules syntax... ✔ database: rules syntax for database todo-sample-xxxxx is valid i hosting[todo-sample-xxxxx]: beginning deploy... i hosting[todo-sample-xxxxx]: found 7 files in build ✔ hosting[todo-sample-xxxxx]: file upload complete i database: releasing rules... ✔ database: rules for database todo-sample-xxxxx released successfully i hosting[todo-sample-xxxxx]: finalizing version... ✔ hosting[todo-sample-xxxxx]: version finalized i hosting[todo-sample-xxxxx]: releasing new version... ✔ hosting[todo-sample-xxxxx]: release complete ✔ Deploy complete! Project Console: https://console.firebase.google.com/project/todo-sample-xxxxx/overview Hosting URL: https://todo-sample-xxxxx.firebaseapp.com
無事デプロイできたので、https://todo-sample-xxxxx.firebaseapp.com にアクセスします。(todo-sample-xxxxは自身のプロジェクトIDに置き換えてください。)
アプリが公開できました!
Firebaseで公開されている内容が更新されていないようなら、 firebase.jsonの内容が正しいかどうか確認してください。以下の記事を参照してください。
これでSTEP 2は終了です。おめでとうございます!STEP 3ではCloud Functions for Firebaseを利用します。
★次回の記事
★目次
データベースルールファイル(database.rules.json)の変更と反映 (STEP 2 : ユーザ認証を行なって、自分だけが読み込めて書き込めるタスク管理アプリを作成する - React + Redux + Firebase チュートリアル)
★前回の記事
データベースルールファイル(database.rules.json)を変更します。
todosのルール
STEP 2で作成したアプリでは、ユーザは自分のタスクのみ見れて、自分のタスクにのみ書き込めるので、ルールをそのように変更します。
database.rules.json
{ "rules": { "todos": { "$uid": { // #1 ".read": "$uid === auth.uid", // #2 ".write": "$uid === auth.uid", // #3 "$todoId": { // #4 ".validate": "newData.hasChildren(['text', 'completed'])", // #5 "text": { ".validate": "newData.isString()" // #6 }, "completed": { ".validate": "newData.isBoolean()" // #7 }, "$other": { ".validate": false } // #8 } } } } }
todos
の直下のパスを$uid
という名前で取得できるようにします(#1)。- 読み取りは、
$uid
と、認証ユーザのuidが同一の場合のみ可能にします(#2)。 - 書き込みは、
$uid
と、認証ユーザのuidが同一の場合のみ可能にします(#3)。 - 各タスクのキーを
$todoId
という名前にします(#4)。 $todoId
の直下のパスは、text
とcompleted
が必須になるようにします(#5)。text
は文字列であるというvalidationを追加します(#6)。実際のプロダクションだと、文字の長さなども定義した方が良いですが、今回は割愛します。completed
は真偽値であるというvalidationを追加します(#7)。text
、completed
以外の値が入らないように、最後に"$other": { ".validate": false }
を追加します(#8)。
usersのルール
usersのデータは今回は使用しないので、簡単に定義して起きます。
database.rules.json
{ "rules": { // 中略 "users": { "$uid": { // #1 ".read": true, // #2 ".write": "$uid === auth.uid" // #3 } }, "$other": { ".validate": false } // #4 } }
- todosの方と同様に、users直下のパスを
$uid
で読み取れるようにします(#1)。 - 読み取りは
true
にします(#2)。 - 書き込みは、
uid
と、認証ユーザのuidが同一の場合のみ可能にします(#3)。 todos
、users
以外の名前で、このパスの下に新しいパスを作れないようにします(#4)。
データベースルールのFirebaseへの反映
ルールをFirebaseに反映します。
$ firebase deploy --only database === Deploying to 'todo-sample-xxxxx’… i deploying database i database: checking rules syntax... ✔ database: rules syntax for database todo-sample-xxxxx is valid i database: releasing rules... ✔ database: rules for database todo-sample-xxxxx released successfully ✔ Deploy complete! Project Console: https://console.firebase.google.com/project/todo-sample-xxxxx/overview
反映されました。
★次回の記事
★目次
タスクの書き込みパスの変更 (STEP 2 : ユーザ認証を行なって、自分だけが読み込めて書き込めるタスク管理アプリを作成する - React + Redux + Firebase チュートリアル)
★前回の記事
タスクの書き込みパスを変更します。
todo actionの変更
書き込みパスを、todos/
からtodos/${uid}
に変更します。念のため、uidが定義されていない時はエラーを出すようにします。
src/actions/index.js
export const NOT_AUTHENTICATED_ON_TODO_ACTION = 'NOT_AUTHENTICATED_ON_TODO_ACTION'; // 追加
src/actions/todoActions.js
import {ADD_TODO_REQUEST, ADD_TODO_SUCCESS, ADD_TODO_ERROR, TOGGLE_TODO_REQUEST, TOGGLE_TODO_SUCCESS, TOGGLE_TODO_ERROR, NOT_AUTHENTICATED_ON_TODO_ACTION} // 追加 from './' // 中略 const notAuthenticatedOnTodoAction = () => ({ type: NOT_AUTHENTICATED_ON_TODO_ACTION }) export const addTodo = (uid, text) => { // #1 return (dispatch, getState, {getFirebase}) => { if (!uid) { // #2 dispatch(notAuthenticatedOnTodoAction()); return; } dispatch(addTodoRequest()); const firebase = getFirebase(); firebase.push(`todos/${uid}`, {completed: false, text}) // #3 // 中略 } } export const toggleTodo = (uid, id) => { // #4 return (dispatch, getState, {getFirebase}) => { if (!uid) { // #5 dispatch(notAuthenticatedOnTodoAction()); return; } const firebase = getFirebase(); const state = getState(); const todo = state.firebase.data.todos[uid][id]; // #6 dispatch(toggleTodoRequest(todo.text, !todo.completed)); firebase.update(`todos/${uid}/${id}`, {completed: ! todo.completed}) // #7 // 中略 } }
addTodo
の引数にuid
を追加します(#1)。uid
が未定義などの場合、エラーを画面に表示するように、dispatchします(#2, #5)。- 書き込みパスを
todos/${uid}
に変更します(#3)。 toggleTodo
の引数にuid
を追加します(#4)。- 選択したtodoのデータの保存場所を変更します(#6)。
- 書き込みを行うパスを、
todos/${id}
から、todos/${uid}/${id}
に変更します(#7)。
reducerの変更
NOT_AUTHENTICATED_ON_TODO_ACTION
に対応するコードを追加します。
src/reducers/todos.js
import {ADD_TODO_REQUEST, ADD_TODO_SUCCESS, ADD_TODO_ERROR, TOGGLE_TODO_REQUEST, TOGGLE_TODO_SUCCESS, TOGGLE_TODO_ERROR, NOT_AUTHENTICATED_ON_TODO_ACTION} // 追加 from '../actions/' // 中略 const todos = (state = {}, action) => { switch (action.type) { // 中略 case NOT_AUTHENTICATED_ON_TODO_ACTION : // 追加 return {...state, notice: 'タスクを追加・変更するにはログインしてください'} // 中略 } }
componentの変更
actionにuidを渡すように、componentを変更します。
src/components/TodoComponent
let TodoComponent = ({uid, authenticating, authenticated}) => { // 中略 return ( <div> <AddTodo uid={uid} /> {/* #1 */} <NoticeForTodo /> <VisibleTodoList uid={uid} /> <Footer /> </div> ) }
AddTodo
にuid
を渡します(#1)。
src/containers/AddTodo.js
// 前略 import PropTypes from 'prop-types' // #1 let AddTodo = ({ uid, dispatch }) => { // #2 // 中略 return ( <div> <form onSubmit={ e => { // 中略 dispatch(addTodo(uid, input.value)) // #3 // 中略 }} > // 中略 } AddTodo.propTypes = { // #4 uid: PropTypes.string.isRequired, }
- PropTypesをimportします(#1)。
AddTodo
コンポーネントの引数に、uid
を追加します(#2)。addTodo
の引数にuid
を追加します(#3)。uid
に対するpropTypesを設定します(#4)。
src/visibleTodoList.js
const mapDispatchToProps = (dispatch, {uid}) => { // #1 return { onTodoClick: (id) => { dispatch(toggleTodo(uid, id)) // #2 } } }
ownProps
からuid
を受け取ります(#1)。toggleTodo
の引数にuid
を追加します(#2)。
動作確認
ログインします。
タスクを追加します。
追加できました。
タスクをさらに追加して、タスク完了フラグを切り替えます。
うまく動きました。
ログアウトしてから、別ユーザでログインします。
タスク追加、タスク完了フラグの切り替えが動作しました。
データベースも確認します。
ユーザごとにタスクが作成されています。STEP 1で作成したタスクは削除してしまいましょう。
以上でユーザ認証を行なって、自分だけが読み込めて書き込めるタスク管理アプリが完成しました!おめでとうございます!
★次回の記事
★目次
タスクの読み込みパスの変更 (STEP 2 : ユーザ認証を行なって、自分だけが読み込めて書き込めるタスク管理アプリを作成する - React + Redux + Firebase チュートリアル)
★前回の記事
データのパスの変更
ユーザごとにデータを保存するように変更します。
今までのパスは、/todo
の下に各タスクを保存していましたが、STEP 2 では
/todos/${uid}
のように、各ユーザIDの下にタスクを保存するように変更します。
VisibleTodoListの変更
uid
はstate,firebase.auth.uid
に保存されています。firebaseConnect
関数はstate
を渡さないので、
uidの値は親コンポーネントから受け渡します。
src/containers/VisibleTodoList.js
const firebaseQueries = ({uid}) => ( // #1 [`todos/${uid}`] ) const mapStateToProps = ({visibilityFilter, firebase: {data : {todos}}}, {uid}) => { return { todos: getVisibleTodos(todos && todos[uid], visibilityFilter) // #2 } }
firebaseQueries
を配列から関数に変更します(#1)。関数はpropsを第1引数に取り、配列を返します。親コンポーネントからuidの値を受け取り、/todos/${uid}
パスのデータを読み取ります。- todosのパスを変更します。firebaseのtodosがnullの場合があるので、
todos &&
の条件を追加します。(#2)
TodoComponentの変更
TodoComponentの変更します。uidの受け渡しをするのと、ログインしていない時に下位コンポーネントを表示しないように変更をします。
src/components/TodoComponent
// 前略 import { connect } from 'react-redux' import { isEmpty, isLoaded } from 'react-redux-firebase' import PropTypes from 'prop-types' let TodoComponent = ({uid, authenticating, authenticated}) => { if (authenticating) { // #4 return <div>ログイン中...</div> } if (!authenticated) { //#5 return <div>タスク一覧を表示するには、ログインしてください。</div> } return ( <div> <AddTodo /> <NoticeForTodo /> <VisibleTodoList uid={uid} /> {/* #6 */} <Footer /> </div> ) } TodoComponent.propTypes = { uid: PropTypes.string, authenticating: PropTypes.bool.isRequired, authenticated: PropTypes.bool.isRequired } const mapStateToProps = ({firebase: {auth, auth: {uid}}}) => ({ uid, // #1 authenticating: !isLoaded(auth), // #”2 authenticated: !isEmpty(auth) // #3 }) TodoComponent = connect( mapStateToProps )(TodoComponent) export default TodoComponent;
- stateからを取得します(#1)。
- 認証情報が読み込み中か(#2)と、認証情報が存在するか(#3)をstateから取得します。
- 認証情報読み込み中にその旨を表示します(#4)。
- ログインしていない時に、タスクはログイン後見られる旨を表示します(#5)。
- uidをVisibleTodoListに渡します(#6)。
動作確認
未ログイン時。タスク追加ダイアログやフィルターも表示されません。
ログイン時。ユーザごとのタスクはまだありません。
以上で読み込みパスの変更ができました。
★次回の記事
★目次
Firebaseを利用した認証機能の作成(2) view (STEP 2 : ユーザ認証を行なって、自分だけが読み込めて書き込めるタスク管理アプリを作成する - React + Redux + Firebase チュートリアル)
★前回の記事
Viewの実装
ログイン機能のviewを作成します。未ログイン時にはGoogleアカウントでログイン
ボタンを表示し、ログイン時には名前とログアウトボタンを表示します。
src/components/Login.js
import React from 'react' import { connect } from 'react-redux' import PropTypes from 'prop-types' import { isLoaded, isEmpty } from 'react-redux-firebase' import { loginWithGoogle, logout } from '../actions/authActions' let Login = ({ auth, loginWithGoogle, logout }) => { if (!isLoaded(auth)) { // #4 return (<div>ログイン中...</div>); } if (isEmpty(auth)) { // #5 return ( <button onClick={loginWithGoogle}>Googleアカウントでログイン</button> ) } return ( <div> {auth.displayName} さん {/* #6 */} <button onClick={logout}>ログアウト</button> {/* #7 */} </div> ); } Login.propTypes = { auth: PropTypes.object.isRequired, loginWithGoogle: PropTypes.func.isRequired, logout: PropTypes.func.isRequired } const mapStateToProps = state => ( { auth: state.firebase.auth } // #1 ) const mapDispatchToProps = dispatch => { return { loginWithGoogle: () => dispatch(loginWithGoogle()), // #2 logout: () => {dispatch(logout()) // #3 } } } Login = connect( mapStateToProps, mapDispatchToProps )(Login) export default Login;
- 認証情報は、
state.firebase.auth
で取得できます(#1)。auth
というプロパティ名でviewに渡します。 - ログイン関連のfunctionをviewに渡します(#2, #3)。
- 認証情報の読み込み中は、
isLoaded(auth)
がfalse
になるので、その間は読み込み中である旨を表示します(#4)。 - ログアウトしている場合は、
isEmpty(auth)
がtrue
になるので、ログインボタンを表示します(#5)。 - ログインしている場合は、ユーザ名(#6)とログアウトボタン(#7)を表示します。
動作確認
ログイン、ログアウトの機能ができたので、動作確認します。
ログアウト状態です。
Googleでログイン
ボタンを押すと、画面が切り替わり、アカウント選択画面になります。
アカウントを選択すると、元の画面に戻り、少しの間ログイン中...
と表示されます。
読み込みが終わると、ログイン名とログアウトボタンが表示されます。
ログアウトを押すと、ログインボタンが表示されます。
Firebaseのデータも確認しておきましょう、
Firebase Console > Authentication > ユーザ
に、ログインしたことのあるユーザ一覧が表示されます。
Database
のデータタブで、/user
パスにユーザの情報が格納されていることが確認できます。
以上で認証機能の作成は完了です。
★次回の記事
★目次
Firebaseを利用した認証機能の作成(1) actionとreducer (STEP 2 : ユーザ認証を行なって、自分だけが読み込めて書き込めるタスク管理アプリを作成する - React + Redux + Firebase チュートリアル)
★前回の記事
認証機能を実装します。react-redux-firebaseのAPIを利用します。
ユーザ情報の保存先の設定
始めにindex.js
のcreateStoreWithFirebase
を変更します。
src/index.js
const createStoreWithFirebase = compose( applyMiddleware(thunk.withExtraArgument({getFirebase})), reactReduxFirebase(firebase, {userProfile: 'users'}) // 変更 )(createStore);
reactReduxFirebaseの引数に{userProfile: 'users’}
を追加します。このようにすると、Firebase Realtime Databaseの/user
パスにユーザのデータが保存されます。(/user
のデータはSTEP 3で使用します。)
ログイン・ログアウトactionの作成
ログインとログアウトのactionを作成します。公式のAuthのページ を参考に進めていきます。
src/actions/index.js
下記を追加します。
// auth actions export const LOGIN_SUCCESS = 'LOGIN_SUCCESS' export const LOGIN_ERROR = 'LOGIN_ERROR' export const LOGOUT_SUCCESS = 'LOGOUT_SUCCESS' export const LOGOUT_ERROR = 'LOGOUT_ERROR'
認証actionを実装します。
src/actions/authActions.js
(新規ファイル)
import { LOGIN_SUCCESS, LOGIN_ERROR, LOGOUT_SUCCESS, LOGOUT_ERROR } from './' const loginSuccess = () => ({ type: LOGIN_SUCCESS }) const loginError = (err) => ({ type: LOGIN_ERROR, err }) const logoutSuccess = () => ({ type: LOGOUT_SUCCESS }) const logoutError = (err) => ({ type: LOGOUT_ERROR, err }) export const loginWithGoogle = () => { return (dispatch, getState, {getFirebase}) => { const firebase = getFirebase(); firebase.login({provider: 'google'}) // #1 .then(() => { dispatch(loginSuccess()); }).catch(err => { dispatch(loginError(err)); }); } } export const logout = () => { return (dispatch, getState, {getFirebase}) => { const firebase = getFirebase(); firebase.logout() // #2 .then(() => { dispatch(logoutSuccess()); }).catch(err => { dispatch(logoutError(err)); }); } }
- ログインを行います(#1)。providerにgoogleを指定します。
- ログアウトを行います(#2)。
actionの中心となるのは、上記の2行のコードだけです。非常に短いコードでログイン、ログアウトが実装できました。
reducerの実装
reducerの実装をします。ログアウト時に設定をクリアします。
src/reducers/todos.js
import { LOGOUT_SUCCESS, ADD_TODO_REQUEST, ADD_TODO_SUCCESS, ADD_TODO_ERROR, TOGGLE_TODO_REQUEST, TOGGLE_TODO_SUCCESS, TOGGLE_TODO_ERROR } from '../actions/' const todos = (state = {}, action) => { switch (action.type) { // 中略 case LOGOUT_SUCCESS : // 追加 return {} default: return state } }
src/reducers/visibilityFilter.js
import { LOGOUT_SUCCESS, SET_VISIBILITY_FILTER } from '../actions/' // 変更 // 中略 const visibilityFilter = (state = VisibilityFilters.SHOW_ALL, action) => { switch (action.type) { // 中略 case LOGOUT_SUCCESS: // 追加 return VisibilityFilters.SHOW_ALL default: return state } }
以上でログインのactionとreducerの実装が完了しました。
★次回の記事
★目次