あいつの日誌β

働きながら旅しています。

Hachioji.pm #63 に行ってきました

https://atnd.org/events/85292

hachioji.pm #63 に参加してきました。#0から始まっているので64回目です。 Hachioji.pm #37 以来なのでちょうど3年ぶりらしい。そんなに時間経ってるんだ...

八王子の居酒屋で未来を体験する

f:id:okamuuu:20170128190351j:plain

LT一覧

酔っ払いながらメモしているので情報が正確でないかもしれませんのでご了承ください。

hkoba: git submodule のお話

独自のディレクトリ構成のサービスに抵抗するために開発したシェルスクリプトを紹介してくれました。

hirobanex: Line bot API のお話

なぜか資料がマインドマップ。ピャッとしたりヒャッとしたり。

hide_o_55: Rust のお話

今年は Rust をやるそうです。次は Rust が来るのか!?

myfinder: Azure なら Perl で Line Bot が書けるお話

Live Coding で Azure Functions を使った Line Bot 実装を紹介してくれました。

からし: メンテナンスの重要性のお話

メンテナンスって大事ですよね。靴磨きに例えたのは斬新。経営者の方々にはぜひ地球と同じぐらいエンジニアの命を大切にしてほしいです。

pandax381: 初参加だけど実は八王子在住のエンジニアのお話

仕事内容が tcpdump な ネットワークとインフラのエンジニアさん。普段私が関わらないレイヤーの話が隣で聞けるのは貴重な体験でした。難しくて内容がよくわからない自分が残念。

私:フロントエンドエンジニアのお話

実はフロントエンドエンジニアってすごくないですか?もうちょっとフォーカスしたほうがいいんじゃないでしょうか。というお話をしました。 react-storybook をもうちょっと紹介しておけばよかったかも。

https://okamuuu.github.io/talks2/storybook-static/

uzulla: エディタのお話

typora でシーケンス図とか作れて便利だったり炎上の話とか

makamaka_at_donzoko: 納期が襲ってこないお話

現実世界でそうあってほしいですね。

f:id:okamuuu:20170128222148j:plain

それから雑談

いろいろな立場の人がいらっしゃるので日頃感じていた疑問を投げかけていました。

雑談1: プログラマ初学者におすすめの言語

注: 酔っ払いが酔っ払い達と会話して酔っ払った時に記録したメモなのでところどころおかしいと感じるところがあると思いますがご了承下さい。

プログラミング初学者におすすめの言語はなんでしょうという質問をしたら色々な人からいろいろな見解が得られたので楽しかったです。「初学者には言語の選択よりもチューターの有無のほうが重要」という意見に概ね全体が同意してました。ということでチューターがいる人はその人が得意な言語でいいんだと思います。

他にも PHP は最初に composer や bundler などの使い方を覚えなくても WEB サービスを作るのに必要なものが入っているのでそういった点ではおすすめという意見をもらいました。開発環境の構築は初学者がつまずくとやる気でないから結構大事なことだと思います。

それから JavaScript だったらフロントエンドもサーバーサイドも使われるので汎用性が高いのでは?という意見もありました。 なんですが、私個人の意見だとちょっと技術の革変が急すぎるのと非同期処理が初心者に大丈夫なんだろうかという心配はあります。 非同期な処理を逐次でループするプログラムを JavaScript で書くのがちょっと面倒..

ということでチューターがいればその人のおすすめする言語がいいと思います。 チューターがいない人は PHP 案外いいのかな、と個人的には思いました。

そういえば hachiojipm の主催者はとても PHP に精通しているのでお困りの方は是非 hachiojipm にいらしてはいかがでしょうか。

雑談2: LL 系の言語の求人なのに、他の LL 系の言語の経歴を考慮しないのはなぜ

注: 酔っ払いが酔っ払い達と会話して酔っ払った時に記録したメモなのでところどころおかしいと感じるところがあると思いますがご了承下さい。

「別にその言語が未経験だから採用しないという事はないと思う。縁故採用というものがあるし、経験がなくても対応できるという実力が証明できれば問題がないのでは?」という意見があり、とても腑に落ちる話でした。 そもそも経歴書だけで「経験がなくても対応できるという実力」があります、という主張が通らないほうが普通なんでしょうね。 そういえば一度 Python の仕事をした時があったのですが面談に行ったら hachiojipm で会いましたよね。みたいな事があったのであれも一種の縁故採用なのかな?

というわけでそんな縁が生まれる事もあったりするので皆さまも是非 hachiojipm にいらしてはいかがでしょうか。

まとめ

職場にもエンジニアが多数いらっしゃるのですが hachiojipm にはより個性の強い人たちが集まるので色々勉強になると思います。 みなさまも是非 hachiojipm にいらしてはいかがでしょうか。

ちなみに

新宿から八王子までは JR よりも京王線のほうが安いです。

React tutorial (9)

Datetime Picker も使用頻度が高い、というよりもほとんどマストなので記事をひとつ追加します。 Bootstrap 風のデザインの react-datetime を最後に紹介します。

github.com

この他にも airbnb/react-dates など沢山モジュールがありますので、各自でいろいろ試すと良いと思います。

依存モジュールを追加

npm install --save moment react-datetime

修正箇所

git diff src/components/Form.js

 import React, { Component } from 'react'
 import { Field, reduxForm } from 'redux-form'
+
 import Select from 'react-select'
 import 'react-select/dist/react-select.css'
 
+import "react-datetime/css/react-datetime.css"
+import DateTime from 'react-datetime'
+
 export const renderField = ({ input, label, type, meta: { touched, error } }) => (
   <div className="form-group">
     <label htmlFor={label}>{label}</label>
@@ -52,3 +56,14 @@ export const renderSelectMultiField = ({ input, label, options, meta: { touched,
     </div>
   )
 }
+
+export const renderDatePickerField = ({ input, label, meta: { touched, error } }) => (
+  <div>
+    <div className="form_label">{label}</div>
+    <div className="textarea_icon">
+      <DateTime value={input.value} onChange={param => input.onChange(param)} dateFormat={"YYYY/MM/DD"} timeFormat={false} />
+    </div>
+    <p className="text-danger">{touched && (error && <span>{error}</span>)}</p>
+  </div>
+)
+

git diff src/containers/CreatePost.js

 import { connect } from 'react-redux'
 import { Field, reduxForm } from 'redux-form'
 import { createPost } from '../actions'
-import { renderField, renderTextAreaField, renderSelectField, renderSelectMultiField } from '../components/Form'
+import { renderField, renderTextAreaField, renderSelectField, renderSelectMultiField, renderDatePickerField } from '../components/Form'

まとめ

という事で、この Tutorial を一通りやってみると、もしかすると SPA を React.js で書けるようになる気がします。なるといいですね。

React tutorial (8)

react-select

前回は redux-form について触れました。 今回は react-select について触れます。

目次

  • 開発環境を準備
  • React の基本的な Life Cycle に触れる
  • redux に触れる
  • redux-saga に触れる
  • react router に触れる
  • npm で公開されている components を導入して echo system を体感する
  • redux-form に触れる
  • react-select に触れる <= 今日やること

今日やること

f:id:okamuuu:20170125231453g:plain

準備

npm install --save react-select

各種ファイル作成

touch src/components/Form.js

create src/components/Form.js

import React, { Component } from 'react'
import { Field, reduxForm } from 'redux-form'
import Select from 'react-select'
import 'react-select/dist/react-select.css'

export const renderField = ({ input, label, type, meta: { touched, error } }) => (
  <div className="form-group">
    <label htmlFor={label}>{label}</label>
    <input className="form-control" {...input} placeholder={label} type={type}/>
    <p className="text-danger">{touched && (error && <span>{error}</span>)}</p>
  </div>
)

export const renderTextAreaField = ({ input, label, type, meta: { touched, error } }) => (
  <div className="form-group">
    <label htmlFor={label}>{label}</label>
    <textarea className="form-control" {...input} placeholder={label} type={type} rows="10"/>
    <p className="text-danger">{touched && (error && <span>{error}</span>)}</p>
  </div>
)

export const renderSelectField = ({ input, label, options,  meta: { touched, error } }) => {
  return (
    <div className="form-group">
      <label htmlFor={label}>{label}</label>
      <Select
        name={input.name}
        value={input.value}
        onChange={input.onChange}
        onBlur={() => input.onBlur(input.value)}
        options={options}
        placeholder="Select"
        simpleValue
        clearable={false}
      />
      <p className="text-danger">{touched && (error && <span>{error}</span>)}</p>
    </div>
  )
}

export const renderSelectMultiField = ({ input, label, options, meta: { touched, error } }) => {
  return (
    <div className="form-group">
      <label htmlFor={label}>{label}</label>
      <Select
        {...input}
        onBlur={() => input.onBlur([...input.value].map(x => (x.value)))}
        options={options}
        multi
      />
      <p className="text-danger">{touched && (error && <span>{error}</span>)}</p>
    </div>
  )
}

edit src/containers/CreatePost.js

import React, {Component} from 'react'
import { connect } from 'react-redux'
import { Field, reduxForm } from 'redux-form'
import { createPost } from '../actions'
import { renderField, renderTextAreaField, renderSelectField, renderSelectMultiField } from '../components/Form'

const userOptions = [
  {value: 1, label: "userOne"},
  {value: 2, label: "userTwo"},
  {value: 3, label: "userThuree"},
]

const tagsOptions = [
  {value: "foo", label: "foo"},
  {value: "bar", label: "bar"},
  {value: "hoge", label: "hoge"},
  {value: "fuga", label: "fuga"},
]

let PostForm = (props) => {
  const { handleSubmit, pristine, submitting } = props
  return (
    <form onSubmit={handleSubmit}>
      <Field name="userId" label="User" component={renderSelectField} options={userOptions} />
      <Field name="title" type="text" label="Title" component={renderField} />
      <Field name="tags" label="Tags" component={renderSelectMultiField} options={tagsOptions} />
      <Field name="body" type="text" label="Body" component={renderTextAreaField} />
      <div style={{marginTop: "30px"}}>
        <button className="btn btn-primary" type="submit" disabled={pristine || submitting}>Submit</button>
      </div>
    </form>
  )
}

const validate = values => {
  const errors = {}

  if (!values.title) {
    errors.title = 'Required'
  } else if (values.title.length > 15) {
    errors.title = 'Must be 15 characters or less'
  }

  if (!values.body) {
    errors.body = 'Required'
  }

  return errors
}

PostForm = reduxForm({
    form: "post",
    validate,
    enableReinitialize: true,
})(PostForm)

class CreatePost extends Component {

  handleSubmit() {
    const params = this.props.form.post.values
    // Note: the resource will not be really created on the server but it will be faked as if.
    this.props.dispatch(createPost({params}))
  }

  render() {
    return (
      <PostForm onSubmit={this.handleSubmit.bind(this)} />
    )
  }
}

const select = state => (state)

export default connect(select)(CreatePost)

まとめ

以上全8回に分けて React.js で SPA の作り方を紹介しました。将来的に自分が React.js の事を忘れた時に備えた記事ですが、どこかで誰かの役に立てばうれしいです。

なのですがそういえば DatePicker の事を書くのを忘れていました。 ということであとちょっとだけ続きます。

React tutorial (7)

今回は redux-form について触れます。 おそらく React, Redux で管理画面作る場合はこの module を使うことになると思います。

github.com

ちなみに react-redux-form という似た名前のモジュールがありますが優劣に関してはここでは言及しません。 やりたいことができるならどっちを選んでも正解だと思います。

準備

npm install --save redux-form
touch src/containers/CreatePost.js

目次

  • 開発環境を準備
  • React の基本的な Life Cycle に触れる
  • redux に触れる
  • redux-saga に触れる
  • react router に触れる
  • npm で公開されている components を導入して echo system を体感する
  • redux-form に触れる <= 今日やること
  • react-select に触れる

各種ファイル作成

create src/containers/CreatePost.js

import React, {Component} from 'react'
import { connect } from 'react-redux'
import { Field, reduxForm } from 'redux-form'
import { createPost } from '../actions'

const renderField = ({ input, label, type, meta: { touched, error } }) => (
  <div className="form-group">
    <label htmlFor={label}>{label}</label>
    <input className="form-control" {...input} placeholder={label} type={type}/>
    <p className="text-danger">{touched && (error && <span>{error}</span>)}</p>
  </div>
)

const renderTextAreaField = ({ input, label, type, meta: { touched, error } }) => (
  <div className="form-group">
    <label htmlFor={label}>{label}</label>
    <textarea className="form-control" {...input} placeholder={label} type={type} rows="10"/>
    <p className="text-danger">{touched && (error && <span>{error}</span>)}</p>
  </div>
)

let PostForm = (props) => {
  const { handleSubmit, pristine, submitting } = props
  return (
    <form onSubmit={handleSubmit}>
      <Field name="title" type="text" label="Title" component={renderField} />
      <Field name="body" type="text" label="Body" component={renderTextAreaField} />
      <div style={{marginTop: "30px"}}>
        <button className="btn btn-primary" type="submit" disabled={pristine || submitting}>Submit</button>
      </div>
    </form>
  )
}

const validate = values => {
  const errors = {}

  if (!values.title) {
    errors.title = 'Required'
  } else if (values.title.length > 15) {
    errors.title = 'Must be 15 characters or less'
  }

  if (!values.body) {
    errors.body = 'Required'
  }

  return errors
}

PostForm = reduxForm({
    form: "post",
    validate,
    enableReinitialize: true,
})(PostForm)

class CreatePost extends Component {

  handleSubmit() {
    const params = this.props.form.post.values
    // Note: the resource will not be really created on the server but it will be faked as if.
    this.props.dispatch(createPost({params}))
  }

  render() {
    return (
      <PostForm onSubmit={this.handleSubmit.bind(this)} />
    )
  }
}

const select = state => (state)

export default connect(select)(CreatePost)

edit src/Root.js

import 'babel-polyfill'
import 'bootstrap'
import 'bootstrap/dist/css/bootstrap.css'
import React from 'react'
import { Provider } from 'react-redux'
import { Router, Route, IndexRoute, browserHistory } from 'react-router'
import { syncHistoryWithStore } from 'react-router-redux'

import configureStore from './store/configureStore'
import App from './containers/App'
import Home from './containers/Home'
import Posts from './containers/Posts'
import CreatePost from './containers/CreatePost'

const store = configureStore()
const history = syncHistoryWithStore(browserHistory, store)

const NotFound = () => (<div><span>NOT FOUND</span></div>)

export default (props) => (
  <Provider store={store}>
    <Router history={history}>
      <Route path="/" component={App}>
        <IndexRoute component={Home} />
        <Route path="posts">
          <IndexRoute component={Posts} />
          <Route path="create" component={CreatePost} />
        </Route>
      </Route>
      <Route path="*" component={NotFound}/>
    </Router>
  </Provider>
)

edit src/actions.js

import { createAction } from "redux-actions"
import api from './api'

export const FETCH_REQUESTED = "FETCH_REQUESTED"
export const FETCH_SUCCESSED = "FETCH_SUCCESSED"
export const FETCH_FAILED    = "FETCH_FAILED"

export const fetchRequested = createAction(FETCH_REQUESTED)
export const fetchSuccessed = createAction(FETCH_SUCCESSED)
export const fetchFailed    = createAction(FETCH_FAILED)

export function getPosts({userId}) {
  return fetchRequested(() => {
    return api.getPosts({userId})
  })
}

export function createPost({params}) {
  return fetchRequested(() => {
    return api.createPost({params})
  })
}

edit src/api.js

import axios from 'axios'

const BASE_URL = window.location.origin

export function getPosts({userId}) {
  return axios.get(`${BASE_URL}/api/posts?userId=${userId}`).then((res) => {
    return { "posts": res.data }
  })
}

export function createPost({params}) {
  return axios.post(`${BASE_URL}/api/posts`, params).then((res) => {
    return { "post": res.data }
  })
}

export default { getPosts, createPost }

edit src/containers/App.js

import React, { Component } from 'react'
import { Link } from 'react-router'
import { connect } from 'react-redux'
import Notifications from 'reapop'
import theme from 'reapop-theme-wybo'

class App extends Component {

  render() {
    return (
      <div className="container">
        <Notifications theme={theme}/>
        <h1>Single Page Application</h1>
        <p><Link to="/">Home</Link> | <Link to="/posts">Posts</Link> | <Link to="/posts/create">Create Post</Link></p>
        <div style={{ marginTop: '1.5em' }}>{this.props.children}</div>
      </div>
    )
  }
}

export default connect(state => (state))(App)

edit src/reducers/index.js

import { combineReducers } from 'redux'
import { routerReducer } from 'react-router-redux'
import { reducer as formReducer } from 'redux-form'
import { reducer as notificationsReducer } from 'reapop'

import { FETCH_SUCCESSED } from '../actions'

const fetchInitialState = {
  posts: []
}

function fetch(state = fetchInitialState, action) {
  switch (action.type) {
  case FETCH_SUCCESSED:
    return Object.assign({}, state, action.payload)
  default:
    return state
  }
}

const rootReducer = combineReducers({
  notifications: notificationsReducer(),
  fetch,
  routing: routerReducer,
  form: formReducer,
})

export default rootReducer

説明

reduxForm 関数でコンポーネントを wrap していますがこれによって this.props.form.post にフォームの状態が保持されるようになります。 この wrap された関数内でしか Field コンポーネントが動かないのでご注意ください。

公式サイトには example が多数掲載されているのでそちらも合わせてご確認ください。

まとめ

今回は redux-form の使い方に触れました。 次回は react-select という cool なコンポーネントと redux-form を共存させる方法について紹介します。

React tutorial (6)

前回は react での routing について触れました。 今回は HTTP 通信が成功した時などに表示する Notification (Flash Message) に触れます。

Notification も色々あるのですが豊富な機能を備えた reapop を紹介します。

github.com

目次

  • 開発環境を準備
  • React の基本的な Life Cycle に触れる
  • redux に触れる
  • redux-saga に触れる
  • react router に触れる
  • npm で公開されている components を導入して echo system を体感する <= 今日やること
  • redux-form に触れる
  • react-select に触れる

今日やること

f:id:okamuuu:20170124213115g:plain

準備

依存モジュールを install します。 redux-thunk は reapop が使うので install しますが今回のチュートリアルでは自身で実装する箇所はありません。

npm install --save redux-thunk font-awesome reapop reapop-theme-wybo

各種ファイル

edit src/store/configureStore.js

import { createStore, applyMiddleware } from 'redux'
import { reducer as notificationsReducer } from 'reapop'
import createSagaMiddleware from 'redux-saga'
import createLogger from 'redux-logger'
import thunk from 'redux-thunk'

import rootReducers from '../reducers'
import rootSaga from "../sagas"

const sagaMiddleware = createSagaMiddleware()
const logger = createLogger()

const middleware = [thunk, sagaMiddleware, logger]
const createStoreWithMiddleware = applyMiddleware(...middleware)(createStore)

export default function configureStore(initialState) {
  const store = createStoreWithMiddleware(rootReducers, initialState)
  if (module.hot) {
    module.hot.accept('../reducers', () => {
      const nextRootReducer = require('../reducers').default
      store.replaceReducer(nextRootReducer)
    })
  }
  sagaMiddleware.run(rootSaga)
  return store
}

edit src/reducers/index.js

import { combineReducers } from 'redux'
import { routerReducer } from 'react-router-redux'
import { reducer as notificationsReducer } from 'reapop'

import { FETCH_SUCCESSED } from '../actions'

const fetchInitialState = {
  posts: []
}

function fetch(state = fetchInitialState, action) {
  switch (action.type) {
  case FETCH_SUCCESSED:
    return Object.assign({}, state, action.payload)
  default:
    return state
  }
}

const rootReducer = combineReducers({
  notifications: notificationsReducer(),
  fetch,
  routing: routerReducer,
})

export default rootReducer

edit src/containers/Posts.js

import React, {Component} from 'react'
import { connect } from 'react-redux'
import Notifications from 'reapop'
import theme from 'reapop-theme-wybo'

import { fetchRequest } from '../actions'
import { PostItem } from '../components/Posts'
import { getPosts } from '../actions'

class Posts extends Component {

  handleButtonClick() {
    this.props.dispatch(getPosts({userId: 1}))
  }

  render() {
    const {posts} = this.props.fetch

    return (
      <div className="container">
        <Notifications theme={theme}/>
        <h2>Posts</h2>
        {posts.map((post) => (
          <PostItem key={post.id} post={post} />
        ))}
        <button className="btn btn-default" onClick={this.handleButtonClick.bind(this)}>Push</button>
      </div>
    )
  }
}

const select = state => (state)

export default connect(select)(Posts)

edit src/sagas.js

import { takeEvery } from "redux-saga"
import { put, call } from "redux-saga/effects"
import { addNotification as notify } from 'reapop'

import {
  FETCH_REQUESTED,
  fetchSuccessed,
  fetchFailed,
} from "./actions"

function notifySuccess(message) {
  return notify({
    message,
    position: 'tc',
    status: 'success',
    dismissAfter: 3000
  })
}

function notifyError(message) {
  return notify({
    message,
    position: 'tc',
    status: 'error',
    dismissAfter: 3000
  })
}

function* fetch(action) {
  try {
    const data = yield call(action.payload)
    yield put(fetchSuccessed(data))
    yield put(notifySuccess("fetch success"))
  } catch (err) {
    yield put(fetchFailed(err))
    yield put(notifyError(err.message))
  }
}

export default function* rootSaga() {
  yield takeEvery(FETCH_REQUESTED, fetch)
}

説明

fetch が成功したらメッセージを表示させます。 今回の tutorial ではこのような書き方をしていますが、 redux-saga を使っているので FETCH_SUCCESSEDFETCH_FAILED を検知して動作させることもできます。

次回は redux-form に触れます。

React tutorial (5)

前回は React で Single Page Application をするためによく使われる redux-saga に触れました。 今回もよく使われる機能である react-router に触れます。

目次

  • 開発環境を準備
  • React の基本的な Life Cycle に触れる
  • redux に触れる
  • redux-saga に触れる
  • react router に触れる <= 今日やること
  • npm で公開されている components を導入して echo system を体感する
  • redux-form に触れる
  • react-select に触れる

準備

react-router と一緒に react-router-redux を install します。

npm install --save react-router react-router-redux

各種ファイル作成

touch src/containers/App.js src/containers/Home.js

create src/containers/App.js

import React, { Component } from 'react'
import { Link } from 'react-router'
import { connect } from 'react-redux'

class App extends Component {

  render() {
    return (
      <div className="container">
        <h1>Single Page Application</h1>
        <p><Link to="/">Home</Link> | <Link to="/posts">Posts</Link></p>
        <div style={{ marginTop: '1.5em' }}>{this.props.children}</div>
      </div>
    )
  }
}

export default connect(state => (state))(App)

create src/components/Home.js

import React, { Component } from 'react'
import { connect } from 'react-redux'

class Home extends Component {

  render() {
    return (
      <div>
        <h2>Home</h2>
        <p>this is Home.js</p>
      </div>
    )   
  }
}

export default connect(state => (state))(Home)

edit src/Root.js

import 'babel-polyfill'
import 'bootstrap'
import 'bootstrap/dist/css/bootstrap.css'
import React from 'react'
import { Provider } from 'react-redux'
import { Router, Route, IndexRoute, browserHistory } from 'react-router'
import { syncHistoryWithStore } from 'react-router-redux'

import configureStore from './store/configureStore'
import App from './containers/App'
import Home from './containers/Home'
import Posts from './containers/Posts'

const store = configureStore()
const history = syncHistoryWithStore(browserHistory, store)

const NotFound = () => (<div><span>NOT FOUND</span></div>)

export default (props) => (
  <Provider store={store}>
    <Router history={history}>
      <Route path="/" component={App}>
        <IndexRoute component={Home} />
        <Route path="posts" component={Posts} />
      </Route>
      <Route path="*" component={NotFound}/>
    </Router>
  </Provider>
)

edit src/reducers/index.js

import { combineReducers } from 'redux'
import { routerReducer } from 'react-router-redux'

import { FETCH_SUCCESSED } from '../actions'

const fetchInitialState = {
  posts: []
}

function fetch(state = fetchInitialState, action) {
  switch (action.type) {
  case FETCH_SUCCESSED:
    return Object.assign({}, state, action.payload)
  default:
    return state
  }
}

const rootReducer = combineReducers({
  fetch,
  routing: routerReducer,
})

export default rootReducer

以下を実行

% node devServer.js

説明

ここでは App.js が template の layout を担当しています。 Navigator のような共通パーツを親コンポーネントに配置して、子となるコンポーネントを URL に応じて振り分けて表示させます。 ここでは紹介しませんが親、子、孫と伝播させることも可能です。

まとめ

今回は Single Page Application の Routing を紹介しました。 次回は npm で利用できる notification を紹介します。