react, redux, react-router, pouchdbによる超簡易版ever note - PouchDB APIの非同期コール，pouch.on('change')による同期，react-routerとの統合

react, redux, react-router, pouchdbの練習として作った超簡易サンプルアプリです．

- 保存したメモのタイトル一覧が見れる
- 一覧画面でタイトルをクリックすると詳細画面に飛ぶ
- メモは複数クライアント間で同期される (via PouchDB)

というような感じです

pouchdbの内容をreduxのstoreに同期させる`pouch-redux-middleware`という便利なパッケージもありますが，今回はこれは使わず直接PouchDBのAPIを叩いています．これは，

- 扱うDBが大きくなってくると，リモートのDBの内容を全てクライアントのstateに保持するのは非効率
- attachmentsを扱うときは直接PouchDBのAPIを触りたい

という理由からです．
今回のサンプルアプリではDBのサイズはそんなに大きくならないし，attachmentsも扱わないので，これらのメリットはありませんが，いずれ大きなアプリを作る際の練習ということで．

リポジトリはこちらになります：
https://github.com/yuichiroTCY/react-redux-pouch-example

以下，解説
大事だと思ったことをバラバラ書いているので一貫性はありません


# Presentational componentとContainer componentの分離

別記事にしました：http://qiita.com/yuichiroTCY/items/a3ca7d9d415049d02d60

本プロジェクトでは，
`src/components`にPresentational componentを，`src/containers`にContainer componentを格納しています．

# Reduxの枠組みでPouchDBを扱う
http://redux.js.org/docs/advanced/AsyncActions.html
のとおりです．
Reduxのdispatchはそのままでは非同期APIコールを含むような処理を扱えません．
`redux-thunk`というMiddlewareを使います．

Middlewareは`src/store/configureStore.js`で設定しています．

その上で，たとえば，`type='note'`なドキュメントの一覧を取得する処理はこのように書けます．

```js:src/actions.js
export const POUCH_REQUEST_FETCH_ALL_NOTES = 'POUCH_REQUEST_FETCH_ALL_NOTES'
export const POUCH_RECEIVE_FETCH_ALL_NOTES = 'POUCH_RECEIVE_FETCH_ALL_NOTES'
export function pouchRequestFetchAllNotes() {
    return {
        type: POUCH_REQUEST_FETCH_ALL_NOTES,
    }
}
export function pouchReceiveFetchAllNotes(res) {
    return {
        type: POUCH_RECEIVE_FETCH_ALL_NOTES,
        noteList: res.rows.map(row => row.doc)
    }
}

export function pouchFetchAllNotes() {
    return dispatch => {
        dispatch(pouchRequestFetchAllNotes())

        return db.query({
            map: ((doc) => {
                emit(doc.type)
            }).toString()
        }, {key: 'note', include_docs: true}).then(res => {
            dispatch(pouchReceiveFetchAllNotes(res))
        }).catch(err => {
            console.log(err)
        })
    }
}
```

この`pouchFetchAllNotes`アクションを一覧表示用コンポーネント`src/components/NoteList.react.js`の`componentWillMount()`で`dispatch()`します．
（正確には，`NoteList.react.js`の`componentWillMount()`から`src/containers/SyncedNoteList.react.js`の`onComponentWillMount()`が呼ばれます．

```js:src/containers/SyncedNoteList.react.js
const mapDispatchToProps = (dispatch) => {
    return {
        onComponentWillMount: () => {
            dispatch(pouchFetchAllNotes())
        },
// 略
    }
}
```

`react-thunk`を使うと，`dispatch()`に関数を渡せるようになります．
`dispatch()`に関数を渡すと，その関数が`dispatch`を引数にして実行されます．
この関数内で非同期処理や参照透過でない処理を書くことができます．

上の例では，まず`dispatch(pouchRequestFetchAllNotes())`して非同期処理のフラグを立て（Reducerは`src/store/reducers/note-list.js`），その後PouchDBに処理をリクエストしています．この処理は`Promise`で非同期に実行されますが，結果が返ってきたタイミングで`then`内の`dispatch(pouchReceiveFetchAllNotes(res))`が実行され，非同期処理フラグをOffにすると同時に結果をstateに格納します（これもReducerは`src/store/reducers/note-list.js`）
非同期処理フラグは`store.getState().noteList.isFetching`ですが，これを参照することで，非同期処理中に画面にLoading表示をすることができます．

Reduxのreducerの処理は参照透過な同期処理でなくてはなりません．
ある時点のstateとactionが与えられたら，次のstateは予測可能に決定されます．

なので，非同期処理・参照透過でない処理（ランダムなID採番など）はreducer以外の場所に書かなくてはいけません．
これをreduxの枠組みで扱うために，`dispatch()`に関数を渡してその中で非同期処理・参照透過でない処理を書く仕組みが必要なのです．

ちなみに，今回のコードでは新規データの`put`時のID採番を`src/containers/NoteList.react.js`に書いてしまっています．本来はこれもactionに含めるべきなのかもしれません．

# React-router
詳細画面のURLに現在閲覧しているノートのIDを持たせ，詳細画面を開いたらその情報を基にPouchDBからデータを取ってくるようにします．
ルーティングに`react-router`を使い，さらに`react-router-redux`を入れることでReduxの枠組みで扱います．
`react-router-redux`はルーティングパラメータをreduxのstateとして扱えるようにするMiddlewareを提供します．
Middlewareの設定は`src/store/configureStore.js`です．

例えば，一覧画面でタイトルをクリックしたら詳細画面に飛ぶ処理は以下のように書けます．

```js:src/containers/SyncedNoteList.react.js
const mapDispatchToProps = (dispatch) => {
    return {
// 略
        onListItemClick: (_id) => {
            dispatch(push('notes/' + _id))
        }
    }
}
```

`push`というAction Creatorが提供されるので，reduxのdispatchとしてページ遷移を書けます．

また，遷移した先の詳細画面はURLが`notes/:noteId`ですが，この`:noteId`が`props.params.noteId`として渡されるので，`componentWillMount()`でこのIDを使って`dispatch(pouchGetNote(id))`します．
`pouchGetNote(id)`は，前節で説明したとおり非同期にPouchDBの処理を行うアクションです．
これによって，ページ遷移と同時にPouchDBにリクエストを投げ，結果をstateに格納して，stateを参照して表示する，という処理になります．リクエスト中はstate内のisFetchingフラグを立てて，画面に"Fetching..."と表示します(`src/components/NoteForm.react.js`)

```js:src/containers/SyncedNoteForm.react.js
const mapStateToProps = (state, ownProps) => {
    return {
        noteId: ownProps.params.noteId,
        isFetching: state.noteForm.isFetching,
        note: state.noteForm.note,
    }
}

const mapDispatchToProps = dispatch => {
    return {
        onComponentWillMount: id => {
            dispatch(pouchGetNote(id))
        },
    }
}
```

```js:src/components/NoteForm.react.js
    componentWillMount() {
        this.props.onComponentWillMount(this.props.noteId)
    },
```

また，前節との合わせ技として，PouchDBの処理が完了したらページ遷移する，みたいなこともできます．
本アプリは，詳細画面で内容を保存したら一覧画面に戻るようになっていますが，その部分の処理は以下のようになっています．

```js:src/containers/SyncedNoteForm.react.js

const mapDispatchToProps = dispatch => {
    return {
// 略
        onSaveClick: (note) => {
            dispatch(pouchPutNote(note))
                .then(() => {
                    dispatch(push('/'))
                })
        },
// 略
    }
}
```

`dispatch(pouchPutNote(note))`の部分ですが，`pouchPutNote(note)`が内部でPouchDBの非同期処理を書いてPromiseを返すので，そのPromiseにそのままメソッドチェーンで非同期処理完了後の処理を書くことができます．
これによって，PouchDBへのPutが完了したあと，`/`へ遷移する，ということができます．

# PouchDBのchangesで自動同期

`src/app.js`で

```js:src/app.js
// Subscribe PouchDB change event
db.changes({
    since: 'now',
    live: true,
}).on('change', change => {
    // Very idiot and inefficient way to sync.
    // In product app, this should be more sophisticated like: http://pouchdb.com/2015/02/28/efficiently-managing-ui-state-in-pouchdb.html
    store.dispatch(pouchFetchAllNotes())
}).on('error', err => {
    console.log(err)
});
```

のようにして，リモートのPouchDBの変更を監視しています．
変更があったら，一覧取得のアクションをdispatchします．
変更があったら一覧を再取得するだけなので，

- 詳細画面で編集中の内容がリモートで変更された場合に対応できない
- 一つ変更がある度に一覧をすべて取得しなおすので非効率

など問題がありますが，自動同期処理を書く叩き台としてこんな感じかな，と．