react-reduxでページ読み込み時、actionを呼び出す方法

Gakuです。
Reduxむずいっす。やばいっす。楽しいっす(´Д` )
ということで、ここ3日くらいはまってたページ読み込み時にactionを呼び出す方法がついにクリアできたので備忘録として残します。

ハマった内容

1.とりあえず、初期描画でデータを呼び出す方法が全くわからず(´Д` )になった。
2.onload使えばいいんじゃね?と閃く!
3.onloadで実装したもののなんか無限ループ起こる(今でも原因不明)
4.reduxにはそういう機能がないんじゃね?と思いreactの記事探し出す。
5.reactにあるcomponentDidMount()ってのが使えそうな雰囲気なのはわかったので実装しようと試みる。
6.ひたすら書き方がわからず2日はまる←本日21時まで。。。飯も食わず。。。

解決法

結局reduxのexample見て、実装したいことと同じようなコードを読んで解決した。
(ここに行き着いておけばもっと簡単に今までできた気がする。。。)
redux初学者は一度動かしてみて、コード読んで、いろいろやってみるとreduxで実装できるようになると思います。
https://github.com/reactjs/redux/blob/master/docs/introduction/Examples.md

実装

containerでこんな感じに記述。

import React from 'react'
import { connect } from 'react-redux'
import { fetchProject } from '../actions/operationProject'

class ProjectList extends React.Component {
  componentDidMount() {
    const { dispatch } = this.props
    dispatch(fetchProject())
  }
  render() {
    const { projects } = this.props
    return (
      <div>
      {projects.saveResult.map(project =>
        <p key={project.id}>
          {project.projectname}
        </p>
      )}
      </div>
    )
  }
}

const mapStateToProps = (state) => {
  return {
    projects: state.projects
  }
}

export default connect(mapStateToProps)(ProjectList)

結局componentDidMount()を使うところまでは合ってたみたい。
redux使うのに、reactの記述方法で書いていいのかな?という迷いが今回のハマりに繋がりました( ;´Д`)

おわりに

書方にすごくこだわりたいと思っているので(たいして綺麗じゃないけど。こだわりだけ)、そのこだわりを捨てた方が良いかと思ったハマりでした。。。
ただ、reactは一歩進むと世界が広がっていくので本当楽しいです。
今回のハマり解消した時は昇天しました。

react-reduxで「dispatch is not a function」にハマった場合の対処法

Gukuです。
React.jsのReduxフレームワークを使用していて、「dispatch is not a function」というエラーに3日ほどハマったので備忘録を記述しておきます。
(このエラーの解消法が英語文献漁りまくっても無かったので。。。)
こんなのです。
スクリーンショット 2016-02-23 5.52.17

エラー解消法

container部分でこんな感じで記述しているとこのエラーが起こります。

import React from 'react'
import { connect } from 'react-redux'
import AddProject from '../containers/AddProject'
import FetchProject from '../containers/FetchProject'
import { fetchProject } from '../actions/operationProject'

let OnLoadProcessing = ({ onLoadFetchProject }) => (
  <div>
    <AddProject />
    <FetchProject />
    <script type="text/javascript" language="JavaScript">
      window.onload = () => {
        onLoadFetchProject()
      }
    </script>
  </div>
)

const mapDispatchToProps = (dispatch,state) => {
  return {
    onLoadFetchProject: () => {
      dispatch(fetchProject()) //ここでエラーdispatchなんて関数ねぇえよ(´Д` )って怒られる。
    }
  }
}

OnLoadProcessing = connect(
  mapDispatchToProps
)(OnLoadProcessing)

export default OnLoadProcessing

解決法としてはこんな感じ。

import React from 'react'
import { connect } from 'react-redux'
import AddProject from '../containers/AddProject'
import FetchProject from '../containers/FetchProject'
import { fetchProject } from '../actions/operationProject'

let OnLoadProcessing = ({ onLoadFetchProject }) => (
  <div>
    <AddProject />
    <FetchProject />
    <script type="text/javascript" language="JavaScript">
      window.onload = () => {
        onLoadFetchProject()
      }
    </script>
  </div>
)

//空でも良いので、mapStateToPropsを記述
const mapStateToProps = (state) => {
  return {
  }
}

const mapDispatchToProps = (dispatch,state) => {
  return {
    onLoadFetchProject: () => {
      dispatch(fetchProject())
    }
  }
}

OnLoadProcessing = connect(
  mapStateToProps, //ここでconnectするのも忘れない
  mapDispatchToProps
)(OnLoadProcessing)

export default OnLoadProcessing

これでdispatch is not a functionのエラーは解消されます(=゚ω゚)ノ
mapStateToPropsで受け取っているstateの情報が無いとdispatchが動かない?ってことなんでしょうか?
なんか良くわかりませんが、解決したので良しとしましょう。
reduxでcontainerを作成する場合、mapStateToProps,mapDispatchToPropsをセットで必ず記述すれば良さそうですね。

おわりに

こんなエラーシラネ。土日と平日2日ぐらいこのエラーに悩まされた。
やはり、記述方法を自分スタイルにしだすとこういったエラーにはまるなと。
(reduxの機構とかまだよくわかってないし。。。)
ただ。。。ただ、これでサーバサイドへの書き込み、読み込みができるようになったので、これで一通りフロントAppは書き出せるかなっと。
辛い3週間が終わりそうです(=゚ω゚)ノ

rails5.0.0を導入する。

Gakuです。
最近rails界隈が熱いっすね〜。
ということで、この波に乗り遅れないためにもrails5を自分のPCにも導入してみました。
とりあえず動くとこまでです。
(※前提 rbenv,ruby,railsが導入されているPCで、あくまでもrails5へのアップデートを行います。)

導入

ruby2.2.3のインストール

まずrails5app専用のフォルダを作成します。
(※ここのフォルダはrails5専用みたいな感じで構築してきます。他フォルダは以前のrailsが使えるようにしていきます。)

mkdir rails5app

最新のrubyのバージョンを確認しインストールします。

rbenv install --list
rbenv install 2.2.3

rails5appフォルダへ移動して以下コマンドで先ほどインストールしたrubyバージョンを指定します。

rbenv local 2.2.3
ruby -v

これで、ここのフォルダはruby2.2.3を使用、それ以外のフォルダは以前のrubyを使用するといった設定を行うことができました。

rails5のインストール

gem install rails --pre

これだけです。簡単ですね。
で、バージョン確認しておきましょう。

gem search ^rails$ -l

*** LOCAL GEMS ***

rails (5.0.0.beta2)

他フォルダでrailsのバージョン確認をしてみてください。
以前のrailsが適用されていることがわかります。
(※rbenvすんばらしぃい!)

rails5アプリケーションの作成

いつも通りrails5アプリを作成し、起動してみます。

rails new hello_world
rails s -b 0.0.0.0

(※vagrant上で起動しているので -b 0.0.0.0をつけないといけないです。ここなんとかならないものか。。。)
でhttp://0.0.0.0:3000へアクセス!
スクリーンショット 2016-02-18 7.24.29

終わりに

新しいWebSocketの機構、apiモード、railsコマンドの一新といろいろ楽しそうです。
なんなんだろうこの魅惑の世界!
railsやっぱ好きです。愛してます(=゚ω゚)
今回は導入だけでしたが、次回は触ってみます。

Reduxでmaterial-ui使ってみる

Gakuです。
昨日から東京は暖かいですね〜。
絶好の引きこもってプログラミング日和です!
さて、今回はGoogleから出しているMaterial-uiというCSSフレームワークを使えるようになったのでその備忘録を掲載します。

構築

導入

フォルダを作成し、その中にpackage.jsonを作成。
以下のように記述します。

{
  "name": "reduxtestapp2",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "browserify": "^13.0.0",
    "express": "^4.13.4",
    "gulp": "^3.9.1",
    "react": "^0.14.7",
    "react-dom": "^0.14.7",
    "redux": "^3.3.1",
    "react-redux": "^4.4.0",
    "babelify": "^6.1.1",
    "vinyl-source-stream": "^1.1.0",
    "gulp-webserver": "^0.9.1",
    "material-ui": "^0.14.4",
    "react-tap-event-plugin": "^0.2.2"
  }
}

で npm installを行いmoduleをインストールします。

ファイル構成

/
|-actions
|
|-components
|     |-App.js
|     |-LeftNav.js
|     |-MobileTearSheet.js
|     |-TopNav.js
|
|-containers
|
|-node_modules
|     |-npm installで導入された各種module(自動格納)
|
|-reducers
|     |-index.js
|
|-bundle.js
|-gulpfile.js
|-index.html
|-index.js
|-package.json

各種ファイル記述

あくまでDEMOなんで、参考にしていただけたらって感じです。
(とりあえず、NavbarとLeftMenubarを設置しただけです。)

<!--index.html-->
<html>
<head>
  <meta charset="utf-8">
  <title>DEMO</title>
</head>
<body>

  <div id="root"></div>

  <script src="bundle.js"></script>
</body>
</html>
//gulpfile.js
var gulp = require('gulp');
var browserify = require('browserify');
var babelify = require('babelify');
var source = require('vinyl-source-stream');
var webserver = require('gulp-webserver');

gulp.task('browserify', function() {
  browserify('./index.js', { debug: true })
    .transform(babelify)
    .bundle()
    .on("error", function (err) { console.log("Error : " + err.message); })
    .pipe(source('bundle.js'))
    .pipe(gulp.dest('./'))
});

gulp.task('watch', function() {
  gulp.watch('./*.jsx', ['browserify'])
});

gulp.task('webserver', function() {
  gulp.src('./')
    .pipe(webserver({
      host: '127.0.0.1',
      livereload: true
    })
  );
});

gulp.task('default', ['browserify', 'watch', 'webserver']);
//index.js
import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import { createStore } from 'redux'
import chatApp from './reducers'
import App from './components/App'

let store = createStore(chatApp)

render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
)
//reducers/index.js
import { combineReducers } from 'redux'

const chatApp = combineReducers({
})

export default chatApp
//components.App.js
import React from 'react'
import TopNav from './TopNav'
import LeftNav from './LeftNav'

const App = () => (
  <div>
    <div>
      <TopNav />
    </div>
    <div>
      <LeftNav />
    </div>
  </div>
)

export default App
//components/LeftNav.js
import React from 'react';
import MobileTearSheet from './MobileTearSheet';
import List from 'material-ui/lib/lists/list';
import ListItem from 'material-ui/lib/lists/list-item';
import ActionGrade from 'material-ui/lib/svg-icons/action/grade';
import ActionInfo from 'material-ui/lib/svg-icons/action/info';
import ContentInbox from 'material-ui/lib/svg-icons/content/inbox';
import ContentDrafts from 'material-ui/lib/svg-icons/content/drafts';
import ContentSend from 'material-ui/lib/svg-icons/content/send';
import Divider from 'material-ui/lib/divider';

const ListExampleSimple = () => (
  <MobileTearSheet>
    <List>
      <ListItem primaryText="Inbox" leftIcon={<ContentInbox />} />
      <ListItem primaryText="Starred" leftIcon={<ActionGrade />} />
      <ListItem primaryText="Sent mail" leftIcon={<ContentSend />} />
      <ListItem primaryText="Drafts" leftIcon={<ContentDrafts />} />
      <ListItem primaryText="Inbox" leftIcon={<ContentInbox />} />
    </List>
    <Divider />
    <List>
      <ListItem primaryText="All mail" rightIcon={<ActionInfo />} />
      <ListItem primaryText="Trash" rightIcon={<ActionInfo />} />
      <ListItem primaryText="Spam" rightIcon={<ActionInfo />} />
      <ListItem primaryText="Follow up" rightIcon={<ActionInfo />} />
    </List>
  </MobileTearSheet>
);

export default ListExampleSimple;
//components/TopNav.js
import React from 'react';
import AppBar from 'material-ui/lib/app-bar';

const AppBarExampleIcon = () => (
  <AppBar
    title="Title"
    iconClassNameRight="muidocs-icon-navigation-expand-more"
  />
);

export default AppBarExampleIcon;
//components/MobileTearSheet.js
import React from 'react';

const MobileTearSheet = React.createClass({

  propTypes: {
    children: React.PropTypes.node,
    height: React.PropTypes.number,
  },

  contextTypes: {
    muiTheme: React.PropTypes.object,
  },

  render() {

    const styles = {
      root: {
        float: 'left',
        marginBottom: 24,
        marginRight: 24,
        width: 260,
      },
      container: {
        border: 'solid 1px #d9d9d9',
        overflow: 'hidden',
      },
    };

    return (
      <div style={styles.root}>
        <div style={styles.container}>
          {this.props.children}
        </div>
      </div>
    );
  },

});

export default MobileTearSheet;

実行

準備ができたら以下のコマンドで実行しhttp://127.0.0.1:8000で確認します。

gulp

こんな感じで出力されれば成功
スクリーンショット 2016-02-14 10.32.56
(※エラーが出てるのはreducerを一個も記述していないせい。1個もreducerを書かないSPAなんて無い!ということなのだろうか。。。)

おわりに

念願のmaterial-ui導入することができました。
正直これを入れたいがためにreactを勉強し出したと言っても過言ではない。
グラフィカルなUIは痺れます。

Reduxがまだまだ理解できていないので、これからは何か作りつつ勉強していきます(=゚ω゚)ノ

参考文献

Material-UI

Reduxチュートリアルを動かすとこまでの雑記

Gakuです。
React.jsのフレームワークであるReduxの環境構築を行ったのでその備忘録です。

チュートリアルは以下のToDoリスト。英語きちぃ(´Д` )
[blogcard url=”http://redux.js.org/index.html”]

環境構築

必要ツールインストール

package.jsonを作成し以下を記述(結構適当、dependenciesだけご参考に)

{
  "name": "reduxtestapp3",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "browserify": "^13.0.0",
    "express": "^4.13.4",
    "gulp": "^3.9.1",
    "react": "^0.14.7",
    "react-dom": "^0.14.7",
    "redux": "^3.3.1",
    "react-redux": "^4.4.0",
    "babelify": "^6.1.1",
    "vinyl-source-stream": "^1.1.0",
    "gulp-webserver": "^0.9.1"
  }
}

で以下コマンド実行

npm install

ファイル構成

/
|-actions
|    |-index.js
|
|-components
|    |-App.js
|    |-Footer.js
|    |-Link.js
|    |-Todo.js
|    |-TodoList.js
|
|-containers
|    |-AddTodo.js
|    |-FilterLink.js
|    |-VisibleTodoList.js
|
|-reducers
|    |-index.js
|    |-todos.js
|    |-visibilityFilter.js
|
|-node_modules
|    |-各種インストールツール(npm installで自動格納されるので触らない)
|
|-index.js
|-index.html
|-gulpfile.js
|-package.json

なげぇ。。。

コーディング

上のファイル構成を行ったらコーディングしていきます。
基本的に以下に載っているコードを記載していきます。
[blogcard url=”http://redux.js.org/docs/basics/ExampleTodoList.html”]

で、ここに掲載されていないindex.htmlとgulpfile.jsを以下のように記述します。

<!--index.html-->

<html>
<head>
  <meta charset="utf-8">
  <title>My-Electron-Redux-Boilerplate</title>
</head>
<body>

  <div id="root"></div>

  <script src="bundle.js"></script>
</body>
</html>
//gulpfile.js
var gulp = require('gulp');
var browserify = require('browserify');
var babelify = require('babelify');
var source = require('vinyl-source-stream');
var webserver = require('gulp-webserver');

gulp.task('browserify', function() {
  browserify('./index.js', { debug: true })
    .transform(babelify)
    .bundle()
    .on("error", function (err) { console.log("Error : " + err.message); })
    .pipe(source('bundle.js'))
    .pipe(gulp.dest('./'))
});

gulp.task('watch', function() {
  gulp.watch('./*.jsx', ['browserify'])
});

gulp.task('webserver', function() {
  gulp.src('./')
    .pipe(webserver({
      host: '127.0.0.1',
      livereload: true
    })
  );
});

gulp.task('default', ['browserify', 'watch', 'webserver']);

ビルド&動作確認

以下コマンドを実行

gulp browserify

ブラウザでindex.htmlのファイルへアクセスし、以下のような感じに出力されればOK
スクリーンショット 2016-02-11 9.17.33

おわりに

死ぬ。英語むずい。みんな環境構築ばらばら。
スタイル崩しすぎてる文献多い。
みんな血迷ってる。
・・・
・・

でも、モダンは楽しい。。。Mなんだなと最近つくづく思う。。。

React.jsとRailsを連携させる(facebook認証編)

Gakuです。
前回までで、サーバサイド側でOpenID認証をRestで認証できるようにしましたので、今回はReactでそれを使用し認証できるようにしてみました。
その時の備忘録です。

実装手順

今回はreact-native-cookiesというライブラリを使用します。
作成したreact-nativeプロジェクト上で以下のコマンドを叩きインストールします。

npm install react-native-cookies

次にインストールしたライブラリをプロジェクトへ適用させるため、以下の手順を踏みます。
1.作成したreact-nativeプロジェクトをXcode上で開きます。
2.以下のような感じで先ほどインストールしたライブラリをプロジェクト上へ持ってきます。
[node-module]-[react-native-cookies]-[RNCookieManagerIOS]をプロジェクト上へaddします。
スクリーンショット 2016-02-07 20.36.01
スクリーンショット 2016-02-07 20.38.16
3.index.ios.jsを編集します。

'use strict';
import React, {
  AppRegistry,
  Component,
  StyleSheet,
  Text,
  View,
  WebView
} from 'react-native';

import CookieManager from 'react-native-cookies';

const LOGIN_URL = "http://localhost:3000/users/auth/facebook";
const HOME_URL = "http://localhost:3000/"

class facebooklogin2 extends Component {
  constructor(props) {
    super(props);
    this.state = {
      loggedIn: false,
      loadedCookie: false
    };
  }

  componentWillMount () {
    CookieManager.getAll((cookie) => {
      let isAuthenticated;
      // If it differs, change `cookie.remember_me` to whatever the name for your persistent cookie is!!!
      if (cookie && cookie.remember_me) {
        isAuthenticated = true;
      }
      else {
        isAuthenticated = false;
      }

      this.setState({
        loggedIn: isAuthenticated,
        loadedCookie: true
      });
    });
  }

  onNavigationStateChange (navState) {
    if (navState.url == HOME_URL) {
      this.setState({
        loggedIn: true,
      });
    }
  }

  render() {
    if (this.state.loadedCookie) {
      if (this.state.loggedIn) {
        return (
          <View style={[styles.container]}>
            <Text>
              Login中!
              Login中!
              Login中!
              Login中!
              Login中!
              Login中!
              Login中!
              Login中!
              Login中!
              Login中!
              Login中!
              Login中!
              Login中!
              次はポストしてみよう!
            </Text>
          </View>
        );
      }
      else {
        return (
          <View style={[styles.container]}>
            <WebView
              ref={'webview'}
              automaticallyAdjustContentInsets={false}
              style={styles.webView}
              url={LOGIN_URL}
              javaScriptEnabledAndroid={true}
              onNavigationStateChange={this.onNavigationStateChange.bind(this)}
              startInLoadingState={true}
              scalesPageToFit={true}
            />
          </View>
        );
      }
    }
    else {
      return (
        <View></View>
      );
    }
  }
}

var styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#F5FCFF',
  }
});

AppRegistry.registerComponent('facebooklogin2', () => facebooklogin2);

動作

以下のような感じでcookieがない時はfacebookログインページへ、ある時はアプリトップページへ遷移するようになります。
スクリーンショット 2016-02-07 20.41.29

サーバサイドへログインしてるかどうか飛ばす。
|-ログインしてなければfacebook認証ページを表示
| |-ログインしたらtopページへ遷移
|
ログインしていればtopページへ遷移

みたいな感じの動作です。

おわりに

正直な話、facebookSDKを使ってかっこいい感じにしたかった。
かなり難しいし文献があまりないのでこれで妥協しようとは思う。
まぁ、そこまでこだわるとこでもないかなと。
本日はReduxからはじまりfacebook認証とはまりにはまった1日でした(´Д` )
鬼畜の道すぎる。。。
ただ、かなり理解できている今日この頃。

参考文献

https://github.com/joeferraro/react-native-cookies
https://github.com/ryanmcdermott/react-native-login