yucatio@システムエンジニア

趣味で作ったものいろいろ

react-router-domのインストールと設定 (STEP 3 : 他のユーザのタスクが見れるタスク管理アプリを作成する - React + Redux + Firebase チュートリアル)

★前回の記事

yucatio.hatenablog.com

ルーティングと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 Integration

今回はredux binding(redux統合)は使用せず、react-router-domのみ使用します。

今回の変更

  • ナビゲーション用のコンポーネントNavbarを追加します。
  • ルート要素が指定された場合は、Dashboardコンポーネントを描画します。
  • /todo/{ユーザID}/todosが指定された場合には、TodoComponentを描画します

f:id:yucatio:20181012224251p:plain

f:id:yucatio:20181012224304p:plain

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, Switchreact-router-domからimportします(#1)。
  • URLと同期させたいコンポーネントBrowserRouterで囲みます(#2)。
  • LoginNavbarは、すべての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 を表示します。

f:id:yucatio:20181012225211p:plain

Dashboardが表示されました

http://localhost:3000/users/aaaa/todos を表示します。

f:id:yucatio:20181012224951p:plain

タスク一覧が表示されました。まだログインしているユーザのタスクが表示されます。

http://localhost:3000/hoge/fuga を表示します。

f:id:yucatio:20181012225439p:plain

Page Not Foundが表示されました。

うまく動いていますね。

以上でreact-router-domのインストールと設定は終了です。

★次回の記事

yucatio.hatenablog.com

★目次

yucatio.hatenablog.com

データベースルールファイル(database.rules.json)の変更と反映 (STEP 3 : 他のユーザのタスクが見れるタスク管理アプリを作成する - React + Redux + Firebase チュートリアル)

★前回の記事

yucatio.hatenablog.com

データベースルールファイルを変更します。

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

反映されました。

★次回の記事

yucatio.hatenablog.com

★目次

yucatio.hatenablog.com

STEP 3での成果物 (STEP 3 : 他のユーザのタスクが見れるタスク管理アプリを作成する - React + Redux + Firebase チュートリアル)

★前回の記事

yucatio.hatenablog.com

STEP 3では、Cloud Functions for Firebaseを使用して、タスクの作成/更新があった時に、"最近の更新”に登録します。 ユーザは"最近の更新"から各ユーザのタスク一覧へジャンプすることができます。

f:id:yucatio:20181011124946p:plain

タスク一覧のURLは、/users/{ユーザID}/todosにするため、react-router-domを使用します。また、react-router-domをreduxと一緒に使うため、connected-react-routerを利用します。

f:id:yucatio:20181011125247p:plain

自分のタスクのみ編集できるように変更もします。

f:id:yucatio:20181011130157p:plain

"最近の更新”は、そのままだとどんどんデータが溜まってしまうので、Cloud Functionsを利用して適宜一定の数以下になるよう、古いデータを削除するようにします。

それでは、STEP 3のチュートリアルを始めましょう。

★次回の記事

yucatio.hatenablog.com

★目次

yucatio.hatenablog.com

Firebaseアプリの公開 (STEP 2 : ユーザ認証を行なって、自分だけが読み込めて書き込めるタスク管理アプリを作成する - React + Redux + Firebase チュートリアル)

★前回の記事

yucatio.hatenablog.com

アプリが完成したので、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に置き換えてください。)

f:id:yucatio:20181006220312p:plain

f:id:yucatio:20181004224928p:plain

アプリが公開できました!

Firebaseで公開されている内容が更新されていないようなら、 firebase.jsonの内容が正しいかどうか確認してください。以下の記事を参照してください。

yucatio.hatenablog.com

これでSTEP 2は終了です。おめでとうございます!STEP 3ではCloud Functions for Firebaseを利用します。

★次回の記事

yucatio.hatenablog.com

★目次

yucatio.hatenablog.com

データベースルールファイル(database.rules.json)の変更と反映 (STEP 2 : ユーザ認証を行なって、自分だけが読み込めて書き込めるタスク管理アプリを作成する - React + Redux + Firebase チュートリアル)

★前回の記事

yucatio.hatenablog.com

データベースルールファイル(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の直下のパスは、textcompletedが必須になるようにします(#5)。
  • textは文字列であるというvalidationを追加します(#6)。実際のプロダクションだと、文字の長さなども定義した方が良いですが、今回は割愛します。
  • completedは真偽値であるというvalidationを追加します(#7)。
  • textcompleted以外の値が入らないように、最後に"$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)。
  • todosusers以外の名前で、このパスの下に新しいパスを作れないようにします(#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

反映されました。

★次回の記事

yucatio.hatenablog.com

★目次

yucatio.hatenablog.com

タスクの書き込みパスの変更 (STEP 2 : ユーザ認証を行なって、自分だけが読み込めて書き込めるタスク管理アプリを作成する - React + Redux + Firebase チュートリアル)

★前回の記事

yucatio.hatenablog.com

タスクの書き込みパスを変更します。

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>
  )
}
  • AddTodouidを渡します(#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)。

動作確認

ログインします。

f:id:yucatio:20181003115453p:plain

タスクを追加します。

f:id:yucatio:20181004151052p:plain

追加できました。

タスクをさらに追加して、タスク完了フラグを切り替えます。

f:id:yucatio:20181004224626p:plain

うまく動きました。

ログアウトしてから、別ユーザでログインします。

f:id:yucatio:20181004224422p:plain

f:id:yucatio:20181004224928p:plain

タスク追加、タスク完了フラグの切り替えが動作しました。

データベースも確認します。

f:id:yucatio:20181004225938p:plain

ユーザごとにタスクが作成されています。STEP 1で作成したタスクは削除してしまいましょう。

以上でユーザ認証を行なって、自分だけが読み込めて書き込めるタスク管理アプリが完成しました!おめでとうございます!

★次回の記事

yucatio.hatenablog.com

★目次

yucatio.hatenablog.com

タスクの読み込みパスの変更 (STEP 2 : ユーザ認証を行なって、自分だけが読み込めて書き込めるタスク管理アプリを作成する - React + Redux + Firebase チュートリアル)

★前回の記事

yucatio.hatenablog.com

データのパスの変更

ユーザごとにデータを保存するように変更します。 今までのパスは、/todoの下に各タスクを保存していましたが、STEP 2 では /todos/${uid}のように、各ユーザIDの下にタスクを保存するように変更します。

VisibleTodoListの変更

uidstate,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)。

動作確認

未ログイン時。タスク追加ダイアログやフィルターも表示されません。

f:id:yucatio:20181003115328p:plain

ログイン時。ユーザごとのタスクはまだありません。

f:id:yucatio:20181003115453p:plain

以上で読み込みパスの変更ができました。

★次回の記事

yucatio.hatenablog.com

★目次

yucatio.hatenablog.com