前回ではReactでアプリの見た目と簡単なページ遷移を設定した.
今回は一気にRedux, Thunk, Firebaseを使っていく.
React + Redux + Firebase を使ってログイン機能あり掲示板アプリ開発①
React + Redux + Firebase を使ってログイン機能あり掲示板アプリ開発② ←今ここ!!!
React + Redux + Firebase を使ってログイン機能あり掲示板アプリ開発③
React + Redux + Firebase を使ってログイン機能あり掲示板アプリ開発④
Reduxを使ってReducer実装
まずはreduxとReactとReduxの接着剤的な役割のreact-reduxをインストールする.
cd marioplan
npm install redux react-redux@5.1.1
インストールが完了したら.src/index.jsに移動してstoreを作成する.Reduxの基礎がわかってる人なら大丈夫だと思うがstoreはアプリ全体の状態を管理するためのものだ.React単体ではコンポネント単位でしか状態を保管できなかった.
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import { createStore } from 'redux'
const store = createStore();
ReactDOM.render(<App />, document.getElementById('root'));
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();
createStoreの引数にはreducerを与えないといけない.reducerがどんなものか忘れてしまった人は
たぶんこれが一番分かりやすいと思います React + Redux のフロー図解 で確認してみて欲しい.
reducerは一般的にたくさんの種類のactionを扱うので1つにまとめると煩雑になってしまいがちだ.なので複数に分けて作成しルートリデューサーに統合してstoreに飲み込ませる.
それではreducerを1つ1つ作っていく.srcフォルダ内にstoreを作成し,さらにその中にreducersを作成.
そこにauthReducer.jsを設置し書き込んでいく.
const initState = {}
const authReducer = (state = initState, action) => {
return state
}
export default authReducer
reducerはアプリがスタートした時に初めて実行されるが,最初はstateがアクティブでないので初期値をデフォルト値として与える必要がある.関数内はというと今はまだstateをreturnするだけにしておく.
他のreducerも作っていく.
const initState = ()
const projectReducer = (state = initState, action) => {
return state
}
export default projectReducer
これらをまとめるルートリデューサーを作っていく.
import authReducer from './authReducer'
import projectReducer from './projectReducer'
import { combineReducers } from 'redux'
const rootReducer = combineReducers({
auth: authReducer,
project: projectReducer
})
export default rootReducer
combineReducerにはオブジェクトを渡すが,この時プロパティがそのままstateのプロパティに追加され,それらはreducerと対応する.
つまりauthReducerはstateのauthプロパティを更新,projectReducerはprojectプロパティを更新する役割を持っている.
ルートリデューサーができたのでindex.jsに戻ってcreateStoreに読み込ませる.
そしてアプリがstoreにアクセスできるようにProviderにstoreを渡す形でAppを囲う.Providerはreact-reduxから提供されていて先ほど説明した通りまさにReactとRedux(store)を繋ぐ接着剤の役割を担っている.
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import { createStore } from 'redux'
import rootReducer from './store/reducers/rootReducer'
import { Provider } from 'react-redux'
const store = createStore(rootReducer);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>
, document.getElementById('root')
);
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();
ダミーデータを置いてreduxの振舞いの確認
ここではprojectReducerにでもダミーデータを置いてProjectListひいてはProjectSummaryにpropsとしてダミーデータを渡してうまく表示させてみたいと思う.
const initState = {
projects: [
{id: '1', title: 'help me find peach', content: 'blah blah blah'},
{id: '2', title: 'collect all the stars', content: 'blah blah blah'},
{id: '3', title: 'egg hunt with yoshi', content: 'blah blah blah'},
]
}
const projectReducer = (state = initState, action) => {
return state
}
export default projectReducer
initStateにprojectsがプロパティのオブジェクト配列を設定.これでprojectReducerはこのダミーデータなstateをreturnする.このreturnされたオブジェクトはrootReducerで定義した通り,state中のprojectプロパティにて管理される.
それではDashboardでダミーデータにアクセスして下位のコンポネントに渡して行こう.
import React, { Component } from 'react'
import Notification from './Notification'
import ProjectList from '../projects/ProjectList'
import { connect } from 'react-redux'
class Dashboard extends Component {
render() {
const { projects } = this.props
return (
<div className="dashboard container">
<div className="row">
<div className="col s12 m6">
<ProjectList projects={projects} />
</div>
<div className="col s12 m5 offset-m1">
<Notification />
</div>
</div>
</div>
)
}
}
const mapStateToProps = (state) => {
return {
projects: state.project.projects
}
}
export default connect(mapStateToProps)(Dashboard);
各コンポネントをstoreとつなげるにはconnectを使う.connectは関数でありreact-reduxから提供されている.Providerといい本当に優秀な接着剤である.
connectにはmapStateToPropsとmapDispatchToPropsという2つの関数を渡せる.今は前者だけを渡す.
-
mapStateToProps・・・コンポネントで使いたいstoreのstateを宣言することでpropsとしてコンポネント内で使えるようにする関数 -
mapDispatchToProps・・・action creatorを使う場合に予めdispatchに噛ませた状態をpropsとしてコンポネント内で使えるようにする関数
const { projects } = this.props
で吸収して
<ProjectList projects={projects} />
でうまくProjectListにpropsとしてダミーデータを渡している.
import React from 'react'
import ProjectSummary from './ProjectSummary'
const ProjectList = ({projects}) => {
return (
<div className="project-list section">
{ projects && projects.map(project => {
return (
<ProjectSummary project={project} key={project.id} />
)
})}
</div>
)
}
export default ProjectList;
同様にpropsとしてprojectsを受け取る.
プロジェクトが増えてくることを考えると,ProjectSummaryをひたすら羅列するのは非常に面倒なのでこれを機にmapを使ってProjectSummaryを呼び出す.
import React from 'react'
const ProjectSummary = ({project}) => {
return (
<div className="card z-depth-0 project-summary">
<div className="card-content grey-text text-darken-3">
<span className="card-title">{project.title}</span>
<p>Posted by the Net Ninja</p>
<p className="grey-text">3rd September</p>
</div>
</div>
)
}
export default ProjectSummary;
タイトルに使用してみる.この状態でnpm startしてみる.

タイトルがダミーデータと一致していれば成功だ.
Reduxで非同期処理を扱う
ダミーデータではなく本物のデータベースからデータを取ってくる時にどこにコードを書けばいいだろうか?
reducer内か?いや,それだとうまくワークしない.データベースへのアクセスには幾分か時間がかかる.その間にreturn stateしてしまうからだ.
では,コンポネント自身か?レンダリングのタイミングでデータベースから取ってきてactionとしてreducerに取り込んでstateを更新できる.しかしこれもまたアクセス時間やらが邪魔して細かな懸念が拭い切れない.
やはりコンポネントの外でデータ,state操作はやりたい.
中核となるstoreがあって,stateからマッピングしてcomponentでpropsをDOMとして組み込める.
もしstateを変更したいなら,componentからactionをdispatchする.actionにはreducerが判断するためのtypeと具体的に何がしたいかのpayloadを含める.んでactionはreducerに渡され,そこでstateが更新され,変更点がレンダリングされコンポネントのビューに反映される.
これが基本のライフサイクルである.そして僕らが考えている「どうやって外部からデータを取ってくるか」だが,
まぁもうわかってると思うが,DISPATCH ACTIONとREDUCERの間である.
actionがdispatchされたら(正確にはまだdispatchされない),一旦止めて非同期処理を実行する.その後,止めていたdispatchを再開し,reducerを実行するという流れだ.
これを可能にするためにredux-thunkと呼ばれるmiddlewareを使用する.
middlewareがさっきの2点間でコードを実行する.このおかげでaction creatorの中で非同期処理が行える.

「actionをdispatchする」から「actionを孕んだaction creatorをdispatchする」というイメージだ.
redux thunkはactionではなく関数を返す.
関数の中では,dispatchを一旦中止し,非同期処理を行いdispatchを再開する.
超要約すると,dispatchにactionの代わりにaction creator渡すと,中で非同期処理とかしてそのレスポンスも含めたactionをreducerに渡してくれるよ.ということ.
解説を省いたけどaction creatorがあまり分からないという人は
今から始めるReact入門 〜 Redux 編: Redux 単体で状態管理をしっかり理解する を参照して欲しい.
Thunkのセットアップ
まずThunkのインストール
npm install redux-thunk
src/index.jsでredux-thunkなるmiddlewareをstoreに読み込む.
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import { createStore, applyMiddleware } from 'redux'
import rootReducer from './store/reducers/rootReducer'
import { Provider } from 'react-redux'
import thunk from 'redux-thunk'
const store = createStore(rootReducer, applyMiddleware(thunk));
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>
, document.getElementById('root')
);
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();
reduxからストアエンハンサーapplyMiddlewareという名前の通りmiddlewareをstoreに適用するための関数もインポートしている.引数には複数のmiddlewareを指定することが可能である.ストアエンハンサーはいくつか存在するがどれもstoreに機能性を提供する.今回の場合はaction creator内で関数を返すことができるようになるという機能性が提供されているわけだ.
それではシンプルなaction creatorを作っていく.storeにactionsフォルダを作成.
そこにprojectActions.jsを設置.これプロジェクトに関するaction creatorを持つファイルである.
export const createProject = (project) => {
return (dispatch, getState) => {
// make async call to database
dispatch({ type: 'CREATE_PROJECT', project})
}
};
action creatorが返す関数の引数にはactionをreducerに送る関数dispatchとstateを読み込むためのgetStateが渡されている.
次にやることはCreateProjectコンポネント内でこのaction creatorをdispatchすることだ.
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { createProject } from '../../store/actions/projectActions'
class CreateProject extends Component {
state = {
title: '',
content: ''
}
handleChange = (e) => {
this.setState({
[e.target.id]: e.target.value
})
}
handleSubmit = (e) => {
e.preventDefault()
this.props.createProject(this.state)
}
render() {
return (
<div className="container">
<form onSubmit={this.handleSubmit} className="white">
<h5 className="grey-text text-darken-3">Create new project</h5>
<div className="input-field">
<label htmlFor="title">Title</label>
<input type="text" id="title" onChange={this.handleChange} />
</div>
<div className="input-field">
<label htmlFor="content">Project Content</label>
<textarea id="content" className="materialize-textarea" onChange={this.handleChange}></textarea>
</div>
<div className="input-field">
<button className="btn pink lighten-1 z-depth-0">Create</button>
</div>
</form>
</div>
)
}
}
const mapDispatchToProps = (dispatch) => {
return {
createProject: (project) => dispatch(createProject(project))
}
}
export default connect(null, mapDispatchToProps)(CreateProject)
mapDispatchToPropsでaction creatorを孕んだdispatchをcreateProjectとしてpropsに渡し,handleSubmit内で使った.
次にactionを受け取るprojectReducerを書いていく.
const initState = {
projects: [
{id: '1', title: 'help me find peach', content: 'blah blah blah'},
{id: '2', title: 'collect all the stars', content: 'blah blah blah'},
{id: '3', title: 'egg hunt with yoshi', content: 'blah blah blah'},
]
}
const projectReducer = (state = initState, action) => {
switch (action.type) {
case 'CREATE_PROJECT':
console.log('created project', action.project)
}
return state
}
export default projectReducer
今はとりあえず新しいプロジェクトを作ったら,コンソールに内容が表示するようにした.

本当はCREATE押したらFire storeにデータをaddして欲しい.
ということでいよいよFirebaseを扱っていこうと思う.
Firebaseセットアップ
firebaseに飛んで適当にログインしてコンソールへ移動.
- 1.
プロジェクトを追加を押す -
- 適当に
プロジェクト名を記入
- 適当に
-
- Googleアナリティクスは今回は使わない.
-
- プロジェクト作成完了
ここからfirebaseとアプリをつなげる作業.
プロジェクトのダッシュボードにてHTMLタグのようなマークをクリック.
-
- アプリの適当なニックネームを記入.Hosting機能はあとで別途設定するのでチェックを外す.
-
- Firebase SDKの追加でコードがババっと出てくるが以下の情報だけをコピー.上の方のはnpmで直接インストールするからいらない.
注意: 下コードはapiKeyしかり諸々が筆者固有のユニークなものなので皆さんにはご自身のブラウザで提示されたコードをコピーして欲しい.
// Your web app's Firebase configuration
var firebaseConfig = {
apiKey: "AIzaSyCZItfqmvg5jjK0JXR9cnoW9xcHmPLWkfs",
authDomain: "net-ninja-marioplan-178f9.firebaseapp.com",
databaseURL: "https://net-ninja-marioplan-178f9.firebaseio.com",
projectId: "net-ninja-marioplan-178f9",
storageBucket: "net-ninja-marioplan-178f9.appspot.com",
messagingSenderId: "240847046405",
appId: "1:240847046405:web:45a4511e8e31622089196c",
measurementId: "G-BJYQ8GHCBL"
};
// Initialize Firebase
firebase.initializeApp(firebaseConfig);
firebaseライブラリをインストール
npm install firebase
firebaseセットアップのためのconfigファイルを作成する.srcにconfigフォルダを作成.
そこにfbConfig.jsを作成し先ほどコピーした内容をそのまま貼り付けて,最初と最後に少し足す.
firebaseのfirestoreとauthを使えるようにインポートする.最後から2行目はfirestoreの初期化.
これはfirebaseライブラリの更新であり,firebaseのタイムスタンプの動作を変更する.これによりあとでコンソールがエラーを吐くのを避けている.
apiKeyなどは他人に見られても大丈夫なのでご安心を.最後の方でfirebase側でセキュリティのルールについても設定する.
import firebase from 'firebase/app'
import 'firebase/firestore'
import 'firebase/auth'
// Your web app's Firebase configuration
var firebaseConfig = {
apiKey: "AIzaSyCZItfqmvg5jjK0JXR9cnoW9xcHmPLWkfs",
authDomain: "net-ninja-marioplan-178f9.firebaseapp.com",
databaseURL: "https://net-ninja-marioplan-178f9.firebaseio.com",
projectId: "net-ninja-marioplan-178f9",
storageBucket: "net-ninja-marioplan-178f9.appspot.com",
messagingSenderId: "240847046405",
appId: "1:240847046405:web:45a4511e8e31622089196c",
measurementId: "G-BJYQ8GHCBL"
};
// Initialize Firebase
firebase.initializeApp(firebaseConfig);
firebase.firestore()
export default firebase;
最後にエクスポートすることでコンポネントでインポートすれば,firebaseとやりとりができるようになる.
Firestoreのデータとコレクション
firestoreはNoSQLと呼ばれるデータベース.firestoreではデータの集まりをコレクションという.
今回のでいうとプロジェクト単体がデータとなりその集合をprojectsというコレクションで管理する.他にもusersとnotificationsというコレクションを作成する.

コレクション内のデータはJavaScriptオブジェクトのような形式をとる.
まずはprojectsコレクションを作成していく.そこにダミーデータを設置し,アプリからアクセスできるようにして行こう.
firebaseプロジェクトのDatabase画面にいき,データベースの作成を押す.
ロックモードかテストモードかを選ばされると思いますが,データへのアクセスやデータの更新を簡単にするためにテストモードを選びます.セキュリティのルールを最後の方で書き換えるので大丈夫.
ロケーション(多分サーバーの場所)はasia-northeast1が東京なのでそれを選んで完了をクリック.
コレクションIDがprojectsなコレクションを作る.
その際の以下のダミープロジェクトを1つ作る.

このプロジェクトにはユニークなIDが振り分けられる.
timeStampプロパティも付けたいのだがそれはあとで実装する.Fieldはあとでも追加できるので.
ReduxとFirebaseを繋ぐ
まずはCreateProjectコンポネントにおいてフォームを入力したら入力内容(プロジェクト)がFirestorに追加されることを目指す.
そのためにはprojectAction.jsアクションクリエータでfirebaseとの非同期処理を書く必要がある.
ここでの実装方法として単純にfirebaseライブラリをインストールしてもいいのだが,reduxとfirebaseの共生のためにデザインされたいくつかのパッケージを代わりにインストールしたいと思う.こいつらの名前をreact-redux-firebase,redux-firestoreという.豪華な名前だ.
react-redux-firebaseはfirebaseサービス全体をバインディングする.
redux-firestoreはreduxとfirestore databaseとをバインディングする.
この2つのパッケージをインストールすることで,firebaseのAPIを使用してデータベースと通信できる.action creatorの中でね.
つまり,firestoreデータベースとstoreを同期することができる.
// ここ最近でreact-redux-firebaseが大きなアップデートをしたようで最新のversionだとエラーを吐くので安全なバージョンを選択.
npm install react-redux-firebase@2.4.1
npm install redux-firestore@0.9.0
インストールが完了したら,action creatorでfirebaseまた,firestoreAPIにアクセスできるようにsrc/index.jsにて読み込む.
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import { createStore, applyMiddleware } from 'redux'
import rootReducer from './store/reducers/rootReducer'
import { Provider } from 'react-redux'
import thunk from 'redux-thunk'
import { getFirestore } from 'redux-firestore'
import { getFirebase } from 'react-redux-firebase'
const store = createStore(rootReducer, applyMiddleware(thunk.withExtraArgument({ getFirebase, getFirestore })));
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>
, document.getElementById('root')
);
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();
withExtraArgumentの引数にインポートしたものを渡すことで,渡したものがredux-thunkから呼び出せる.つまりaction creatorで渡したものを使える.つまり,action creator内でfirebaseとの非同期処理が行える.
早速projectActionsをいじる.
export const createProject = (project) => {
return (dispatch, getState, { getFirebase, getFirestore }) => {
// make async call to database
dispatch({ type: 'CREATE_PROJECT', project})
}
};
だがこのままでは2つのget~~は機能しない.なぜなら,こいつらは僕らのfirebaseプロジェクトを知らない.
なので先ほど作ったfirebaseプロジェクトの情報を持ったfbConfigファイルを2つのパッケージに教えてやらないといけない.
そのためにはストアエンハンサーが必要だ.
ストアエンハンサーについては前に少し書いたが,index.jsでいうと,thunkがミドルウェアでapplyMiddlewareがストアエンハンサーである.
つまりstoreに複数のストアエンハンサーを設定することになるのだが,そのためにはreduxからcomposeなるものを使ってまとめる必要がある.
複数のreducerをcombineReducersを使ってまとめたように.
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import { createStore, applyMiddleware, compose } from 'redux'
import rootReducer from './store/reducers/rootReducer'
import { Provider } from 'react-redux'
import thunk from 'redux-thunk'
import { reduxFirestore, getFirestore } from 'redux-firestore'
import { reactReduxFirebase, getFirebase } from 'react-redux-firebase'
import fbConfig from './config/fbConfig'
const store = createStore(rootReducer,
compose(
applyMiddleware(thunk.withExtraArgument({ getFirebase, getFirestore })),
reduxFirestore(fbConfig),
reactReduxFirebase(fbConfig)
)
);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>
, document.getElementById('root')
);
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();
firebase用の2つのパッケージからreduxFirestore, reactReduxFirebaseというストアエンハンサーをインポートしている.
そしてfbConfigを渡すことでprojectActionsに渡されたgetFirebaseとgetFirestoreが僕らのfirebasプロジェクトを認識してくれる.
Firestoreにデータを追加してみる
firebasとreduxの接続設定が完了したので,実際に非同期処理を実装してみよう.
export const createProject = (project) => {
return (dispatch, getState, { getFirebase, getFirestore }) => {
// make async call to database
const firestore = getFirestore();
firestore.collection('projects').add({
...project,
authorFirstName: 'Net',
authorLastName: 'Ninja',
authorId: 12345,
createdAt: new Date()
}).then(() => {
dispatch({ type: 'CREATE_PROJECT', project})
}).catch((err) => {
dispatch({ type: 'CREATE_PROJECT_ERROR', err })
})
}
};
まずgetFirestoreの初期化することでfirestoreインスタンスが手に入る.
firestore.collection('projects').addのところはもう言わずもがな,projectsコレクションにaddの引数のオブジェクトを追加する命令.
この命令は非同期で少なからず時間がかかる.そして終わるまではdispatchするのを待って欲しいので,thenを使う.またエラーが起きた用のcatchでエラー用のアクションをdispatchするようにもした.
次にreducerをいじる.
const initState = {
projects: [
{id: '1', title: 'help me find peach', content: 'blah blah blah'},
{id: '2', title: 'collect all the stars', content: 'blah blah blah'},
{id: '3', title: 'egg hunt with yoshi', content: 'blah blah blah'},
]
}
const projectReducer = (state = initState, action) => {
switch (action.type) {
case 'CREATE_PROJECT':
console.log('created project', action.project);
return state;
case 'CREATE_PROJECT_ERROR':
console.log('create project error', action.err);
return state;
default:
return state;
}
}
export default projectReducer
とりあえずコンソール出力してreturn stateするだけ.
firestoreとつながったか試しにプロジェクトを作ってみよう.

コンソールされたのを確認してfirestoreへいくと.

無事firestoreにデータを追加することに成功した.
しかしアプリのダッシュボードには依然としてダミーデータしか載ってない.次はダミーデータでなくfirestoreのprojectsコレクションのデータを取得して表示するようにしたい.
Firestoreデータとの同期
そのためにはreduxのstateとfirestoreデータを同期する必要がある.そのための必要なパッケージは先ほどインストールしたredux-firestoreである.
どこで使うのかというとrootReducer.jsである.
redux-firestoreから予め定義されたfirestoreReducerというリデューサーをインポートしてcombineReducersしてやるのだ.
こいつはもう僕らのfirebaseプロジェクトを知っているから,バックグランドでfirestoreデータベースとreduxのstateとを同期してくれる.
僕らがしないといけないのはcombineReducersで固有のプロパティを与えてやることくらいだ.
import authReducer from './authReducer'
import projectReducer from './projectReducer'
import { combineReducers } from 'redux'
import { firestoreReducer } from 'redux-firestore'
const rootReducer = combineReducers({
auth: authReducer,
project: projectReducer,
firestore: firestoreReducer
})
export default rootReducer
これでstateのfirestoreプロパティにfirestoreデータベースが自動的に同期されるようになった.
同期するデータはその瞬間にどのコンポネントがアクティブなのかということに依存する.つまりどんなデータをコンポネントが欲するかに依存する.
そして所望されたデータがfirestoreReducerによって同期されるというわけだ.
なので今後僕らが同期のためにやるのはコンポネントからfirestoreReducerに対してどんなデータが欲しいかを伝えることだけだ.
Dashboardでダミーデータの代わりにfirestoreのデータを表示するようにいじって行こう.
import React, { Component } from 'react'
import Notification from './Notification'
import ProjectList from '../projects/ProjectList'
import { connect } from 'react-redux'
import { firestoreConnect } from 'react-redux-firebase'
import { compose } from 'redux'
class Dashboard extends Component {
render() {
const { projects } = this.props
return (
<div className="dashboard container">
<div className="row">
<div className="col s12 m6">
<ProjectList projects={projects} />
</div>
<div className="col s12 m5 offset-m1">
<Notification />
</div>
</div>
</div>
)
}
}
const mapStateToProps = (state) => {
console.log(state);
return {
projects: state.firestore.ordered.projects
}
}
export default compose(
connect(mapStateToProps),
firestoreConnect([
{ collection: 'projects' }
])
)(Dashboard);
コンポネントとfirestoreをつなげるためにreact-redux-firebaseからfirestoreConnectをインポートする.
これをハイオーダーコンポネント(以下HOC)としてDashboardと繋げたいのだが,connect(mapStateToProps)という別のHOCを使用している.2つのHOCを使用するためにはストアエンハンサーの統合に使ったcomposeをここでも使う.
ちなみにfirestoreConnectはオブジェクト配列を受け取る.このオブジェクトではどのコレクションと同期したいかを宣言している.これでrootReducerで宣言した通りstateのfirestoreに所望したデータが保管される.
コンポネントをロードしたとき,firestoreデータが更新された時にfirestoreReducerを誘発しprojectsコレクションとstore stateを同期する.
またmapStateToPropsでprojectsに渡す値をダミーデータでなくfirestoreのデータに変更している.
プロジェクトディティールへの連奏
次はダッシュボードのプロジェクトをクリックしたらディティール画面へ遷移するようにしたい.
プロジェクトをマッピングしているのはProjectListコンポネントなのでそこでProjectSummaryコンポネントをLinkタグで囲めばいい.
import React from 'react'
import ProjectSummary from './ProjectSummary'
import { Link } from 'react-router-dom'
const ProjectList = ({projects}) => {
return (
<div className="project-list section">
{ projects && projects.map(project => {
return (
<Link to={'/project/' + project.id} key={project.id}>
<ProjectSummary project={project} />
</Link>
)
})}
</div>
)
}
export default ProjectList;
なんとか遷移は成功した.次にProjectDetailsコンポネントを整形していく.
firestoreのデータを使うためにDashboardでしたようにHOCをcomposeする.
mapStateToProps部分だが,idにurlのid部分を代入し,それとprojectsを元に今回表示したいプロジェクトを特定しpropsとしてコンポネントに渡している.
コンポネントではprojectがローディング中にelseの方が実行され,ロード完了次第,ifがtrueの方が実行される.
なので画面をリロードすると一瞬elseの文が表示される.
import React from 'react'
import { connect } from 'react-redux'
import { firestoreConnect } from 'react-redux-firebase'
import { compose } from 'redux'
const ProjectDetails = (props) => {
const { project } = props;
if (project) {
return (
<div className="container section project-details">
<div className="card z-depth-0">
<div className="card-content">
<span className="card-title">{ project.title }</span>
<p>{ project.content }</p>
</div>
<div className="card-action gret lighten-4 grey-text">
<div>Posted by {project.authorFirstName} {project.authorLastName}</div>
<div>2nd, September, 2am</div>
</div>
</div>
</div>
)
} else {
return (
<div className="container center">
<p>Loaging project...</p>
</div>
)
}
}
const mapStateToProps = (state, ownProps) => {
const id = ownProps.match.params.id;
const projects = state.firestore.data.projects;
const project = projects ? projects[id] : null
return {
project: project
}
}
export default compose(
connect(mapStateToProps),
firestoreConnect([
{ collection: 'projects' }
])
)(ProjectDetails);
今回はここまで.
次回からはFirebase Authを使ってログイン機能の実装をしていく.

