"Parse and React"を使ってみた


#"Parse and React"とは
ParseもReactもFacebook製ということで、両者をうまく連携してくれるモジュールが存在します。
[Parse and React, a Shared Chemistry](http://blog.parse.com/learn/parse-and-react-shared-chemistry/)

##githubでの説明
https://github.com/ParsePlatform/ParseReact
>Parse + React is an interface layer on top of the Parse JS SDK that provides simple access to the Parse API from React. It lets React components subscribe to Parse queries, and allows data mutations to be dispatched in a Flux-style manner. In the background, these subscriptions are managed in a way that lets these components automatically update as objects are created and modified, allowing user interfaces to be snappy and responsive.

##なにがうれしいの？
observeに登録した変数はParseのQueryと紐付けられ、ComponentがmountしたときとQueryに関係するObjectを変更したときにbackgroudでQueryが再実行されrenderが呼ばれます。
変更の反映を自動でやってくれるので便利です。

Parse+Reactを使っていないときはAction->APIUtil->Action->Store->Componentという流れでデータを更新、反映させていました。
Parse+Reactを使うとComponentのなかだけで完結できるのですっきりします。

#ES6スタイルで書いてみた
Parse+Reactはmixinを使っているのですが、classスタイルでReactを書くとmixinが使えません。
が、'react-mixin'というモジュールを使えばclassスタイルでもmixinが使えるようになります。
*最初なかなか値が反映されてくれなくて困ったのですが、'react-mixin'と'parse-react'のバージョンを最新にしたら機能するようになりました。。

[追記]
parse-reactのversion0.3.1からES6もサポートされるようになったのでサンプルを書き換えました。
https://github.com/ParsePlatform/ParseReact/blob/master/docs/api/ES6.md

##sample code
```js
import React from 'react/addons';
import QuestionListItem from './QuestionListItem.react';
import MessageComposer from './MessageComposer.react';
import { Parse } from 'parse';
import ParseReact from 'parse-react';
import ParseComponent from 'parse-react/class';
//import ReactMixin from 'react-mixin';
import ActionCreators from '../../actions/ActionCreators';
import RouterContainer from '../../services/RouterContainer';
import QuestionStore from '../../stores/QuestionStore';

class QuestionSection extends ParseComponent {

  constructor(props) {
    super(props);
    this.state = {error: null};
    this.onClickQuestion = this.onClickQuestion.bind(this);
  }

  observe(props, state) {
    return { questions: (new Parse.Query('Question')).descending('createdAt').limit(10) };
  }
  
  get questionListItems() {
    return this.data.questions.map(function(question) {
    //question作成直後の更新時はquestion.objectIdが入っていないので仮の値を入れておく
      var key = question.objectId;
      if (key == null) {
        key = 'tempObjectId';
      }
      return (
        <QuestionListItem
          key={key}
          question={question}
          onClickQuestion={this.onClickQuestion}
        />
      );
    }, this);
  }
  
  get content() {
    if (this.data.questions.length) {
      return <ul>{this.questionListItems}</ul>;
    } else if (!(QuestionStore.getQuestions() == null)) {
      //再読み込みする前にStoreにあるquestionsを使うので表示が若干早くなる
      this.data.questions = QuestionStore.getQuestions();
      return <ul>{this.questionListItems}</ul>;
    } else if (this.pendingQueries().length) {
      return <div className='loading' />;
    }
    return (
      <div className='emptyTable'>
        <h2>まだ投稿がありません (´・ω・｀)</h2>
      </div>
    );
  }
  
  render() {
    return (
      <div className="questionSection">
        <MessageComposer submit={this.submitQuestion} />
          {
            this.state.error ?
            <div className='row centered errors'>{this.state.error}</div> :
            null
          }
        <div className="q-list">
          {this.content}
        </div>
      </div>
    );
  }

  submitQuestion(message) {
    ParseReact.Mutation.Create('Question',
    {
      message: message,
      user: Parse.User.current()
    })
    .dispatch()
    .then(() => {/*observeしておけば自動で反映されるので成功時は何もしなくていい*/})
    .fail((error) => this.setState({error: error.message}));//TODO:error handling
  }

  onClickQuestion(question) {
    ActionCreators.setCurrentQuestion(question);
    ActionCreators.setQuestions(this.data.questions);
    RouterContainer.get().transitionTo('/answer/:id', {id:question.objectId}, {});
  }

}

//ReactMixin(QuestionSection.prototype, ParseReact.Mixin);
export default QuestionSection;

```