前回では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を使ってログイン機能の実装をしていく.