react-router-domのインストールと設定 (STEP 3 : 他のユーザのタスクが見れるタスク管理アプリを作成する - React + Redux + Firebase チュートリアル)
★前回の記事
ルーティングとreact-router-domとredux統合
アプリににルーティング機能を追加します。 reactにおけるルーティングと、なぜルーティングが必要なのかはこちらがわかりやすいです。
React Router v4 の基本的な考え方と使い方 - 30歳からのプログラミング
reactにおけるルーティングといえば、react-routerですが、version 4からコア機能のreact-routerとweb用のreact-router-dom、React Native用のreact-router-nativeに分かれたようなので、今回はweb用のreact-router-domを利用します。
reduxとreact-routerを統合するのには、react-router-reduxが有名ですが、2018年10月現在非推奨となっています。 また、react-routerの公式ページには、ルーティング情報をreduxのstoreに保存することは非推奨と書いてあります。
今回はredux binding(redux統合)は使用せず、react-router-domのみ使用します。
今回の変更
- ナビゲーション用のコンポーネント
Navbar
を追加します。 - ルート要素が指定された場合は、
Dashboard
コンポーネントを描画します。 /todo/{ユーザID}/todos
が指定された場合には、TodoComponent
を描画します
react-router-domのインストール
$ yarn add react-router-dom
App.jsの書き換え
ルーティングをApp.js
に記載します。
src/components/App.js
(コメントは消してからデプロイしてください)
import React from 'react' import { BrowserRouter, Route, Switch } from 'react-router-dom' // #1 import Login from './Login' import Navbar from './Navbar' import Dashboard from './Dashboard' import TodoComponent from './TodoComponent' import NoMatch from './NoMatch' const App = () => ( <BrowserRouter> {/* #2 */} <div> <Login /> {/* #3 */} <Navbar /> {/* #3 */} <Switch> {/* #4 */} <Route exact path="/" component={Dashboard} /> {/* #5 */} <Route exact path="/users/:uid/todos" component={TodoComponent} /> {/* #6 */} <Route component={NoMatch} /> {/* #7 */} </Switch> </div> </BrowserRouter> ) export default App;
BrowserRouter, Route, Switch
をreact-router-dom
からimportします(#1)。- URLと同期させたいコンポーネントを
BrowserRouter
で囲みます(#2)。 Login
、Navbar
は、すべてのApp
コンポーネントで表示するので、Switch
の外に書いておきます(#3)。Switch
は、配下のRoute
要素で最初にマッチしたpathのコンポーネントを描画します(#4)。/
パスにマッチした場合は、Dashborad
コンポーネントを描画します(#5)。exact
を付けて、/
だけにマッチするようにします。(exact
がないと、/aaa/bbb
などにもマッチします。)/users/:uid/todos
にマッチした時に、TodoComponent
を描画します(#6)。:uid
の部分はパラメータになっており、/users/user_a/todos
などにマッチします。path
要素なしのRoute
コンポーネントは、すべてのパスにマッチします(#7)。上記のどのパスにもマッチしなかった場合に、NoMatch
コンポーネントが描画されます。
Navbar, Dashbord, NoMatchの実装
Navbar, Dashbord, NoMatchは、仮の実装しておきます。Navbar, Dashboardは後で実装します。
src/components/Navbar.js
import React from 'react' const Navbar = () => ( <div>Navbar</div> ) export default Navbar;
src/components/Dashboard.js
import React from 'react' const Dashboard = () => ( <div>Dashbord</div> ) export default Dashboard;
src/components/NoMatch.js
import React from 'react' const NoMatch = () => ( <div>Page Not Found</div> ) export default NoMatch;
動作確認
http://localhost:3000 を表示します。
Dashboardが表示されました
http://localhost:3000/users/aaaa/todos を表示します。
タスク一覧が表示されました。まだログインしているユーザのタスクが表示されます。
http://localhost:3000/hoge/fuga を表示します。
Page Not Foundが表示されました。
うまく動いていますね。
以上でreact-router-domのインストールと設定は終了です。
★次回の記事
★目次
データベースルールファイル(database.rules.json)の変更と反映 (STEP 3 : 他のユーザのタスクが見れるタスク管理アプリを作成する - React + Redux + Firebase チュートリアル)
★前回の記事
データベースルールファイルを変更します。
todosのルール
STEP 3では他のユーザのタスクは見れるので、そのように変更します。
database.rules.json
{ "rules": { "todos" : { "$uid": { ".read": true, // 変更 // 後略 } } } }
recentUpdatedTodosのルール
/recentUpdatedTodos
に最近更新されたタスクを保存するので、設定を追加します。
database.rules.json
{ "rules": { // 中略 "recentUpdatedTodos": { // 追加 ".read": true, ".write": false // #1 }, "$other": { ".validate": false } } }
recentUpdatedTodos
には、Cloud Functionsからのみ書き込むので、書き込み権限はfalse
になっています。
データベースルールの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 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)。
動作確認
未ログイン時。タスク追加ダイアログやフィルターも表示されません。
ログイン時。ユーザごとのタスクはまだありません。
以上で読み込みパスの変更ができました。
★次回の記事
★目次