How to build Rails5 API + Redux ToDo Application その2
前回はRails5でToDOリストのREST APIを実装しました。
今回はクライアント側の実装をしてみます。
イメージ
言語・ツール等
- ES6 (ES2015)
- React
- gulp
- webpack
. ├── components │ ├── todo-box.js │ ├── todo-form.js │ ├── todo-list.js │ └── todo.js └── main.js
コンポーネントの構成
各パーツをコンポーネント化していきます。
実装してみる
まずTodoから。StateとしてCompleted(完了/未完了)状態を持っています。
完了時にバックカラーが変更され削除時には列ごと消えます。
// components/todo.js import React, { Component } from 'react' export default class Todo extends Component { constructor(props) { super(props) this.state = { completed: props.completed } } rawMarkup() { var rawMarkup = marked(this.props.children.toString(), { sanitize : true }) return { __html: rawMarkup } } render() { return ( <tr className={this.state.completed ? "success": ""}> <td> <input type="checkbox" onChange={(e) => this.props.onTodoCompleted(this, e.target.checked)} checked={this.state.completed ? "checked": ""} /> </td> <td>{this.props.order}</td> <td><span dangerouslySetInnerHTML={this.rawMarkup()} /></td> <td> <input className="btn btn-danger" type="button" onClick={() => this.props.onTodoDelete(this.props.id)} value="削除" /> </td> </tr> ) } }
Todoを管理するTodoListを作成します。
// components/todo-list.js import React, { Component } from 'react' import Todo from './todo' export default class TodoList extends Component { constructor(props) { super(props) } render() { let todoNodes = this.props.data.map(todo => { return ( <Todo url={this.props.url} completed={todo.completed} order={todo.order} id={todo.id} key={todo.id} onTodoDelete={this.props.onTodoDelete} onTodoCompleted={this.props.onTodoCompleted} > {todo.title} </Todo> ) }) return ( <div className="row"> <table className="todoList table table-striped"> <thead> <tr> <th width="10%">完了</th> <th width="20%">優先順位</th> <th>やること</th> <th width="20%"></th> </tr> </thead> <tbody> {todoNodes} </tbody> </table> </div> ) } }
次にTodoFormです。優先順位とTodoを入力して登録する普通のフォームです。
// components/todo-form.js import React, { Component } from 'react' export default class TodoForm extends Component { constructor(props) { super(props) this.state = { order: 1, title: '' } } handleOrderChange(e) { this.setState({ order: e.target.value }) } handleTitleChange(e) { this.setState({ title: e.target.value }) } handleSubmit(e) { e.preventDefault() let order = this.state.order let title = this.state.title.trim() if (!order || !title) { return } this.props.onTodoSubmit({ order: order, title: title, completed: false }) this.setState({ order: 1, title: '' }) } render() { return ( <div className="row"> <form className="todoForm form-horizontal" onSubmit={this.handleSubmit.bind(this)}> <div className="form-group col-md-2"> <input id="todo-order" className="form-control" type="number" value={this.state.order} onChange={this.handleOrderChange.bind(this)} /> </div> <div className="form-group col-md-8"> <input id="todo-title" className="form-control" type="text" value={this.state.title} onChange={this.handleTitleChange.bind(this)} /> </div> <div className="col-md-2"> <input className="btn btn-primary" type="submit" value="登録" /> </div> </form> </div> ) } }
んでこれらを管理するTodoBoxを作成します。
GET, POST, PATCH, DELETE処理はすべてここに実装しています。
これらを下位のコンポーネントに引き渡して状態を更新していく流れです。
import React, { Component } from 'react' import TodoList from './todo-list' import TodoForm from './todo-form' export default class TodoBox extends Component { constructor(props) { super(props) this.state = { data: [] } } loadTodosFromServer() { $.ajax({ url: this.props.url, dataType: 'json', cache: false, success: (data) => { this.setState({ data: data }) }, error: (xhr, status, err) => { console.error(this.props.url, status, err.toString()) } }) } handleTodoSubmit(todo) { todo.id = Date.now() let todos = this.state.data let newTodos = todos.concat([todo]) this.setState({ data: newTodos }) $.ajax({ url: this.props.url, dataType: 'json', type: 'POST', data: { todo: todo }, success: (data) => { this.loadTodosFromServer() }, error: (xhr, status, err) => { this.setState({ data: todos }) console.error(this.props.url, status, err.toString()) } }) } handleTodoCompleted(todo, completed) { $.ajax({ url: this.props.url + '/' + todo.props.id, dataType: 'json', method: 'PATCH', data: { todo: { completed: completed } }, success: (data) => { todo.setState({ completed: data.completed }) }, error: (xhr, status, err) => { console.error(this.props.url, status, err.toString()) } }) } handleTodoDelete(id) { $.ajax({ url: this.props.url + '/' + id, dataType: 'json', method: 'DELETE', success: (data) => { this.loadTodosFromServer() }, error: (xhr, status, err) => { console.error(this.props.url, status, err.toString()) } }) } componentDidMount() { this.loadTodosFromServer() // setInterval(this.loadTodosFromServer.bind(this), this.props.pollInterval) } render() { return ( <div className="todoBox"> <h1>Todoリスト</h1> <TodoForm onTodoSubmit={this.handleTodoSubmit.bind(this)} /> <TodoList data={this.state.data} url="/todos" onTodoCompleted={this.handleTodoCompleted.bind(this)} onTodoDelete={this.handleTodoDelete.bind(this)} /> </div> ) } }
エントリポイントでこれらのコンポーネントを読み込みます。
import React from 'react' import { render } from 'react-dom' import TodoBox from './components/todo-box' let content = document.getElementById('todos') render( <TodoBox url="/todos" pollInterval={2000} />, content )
webpackしたものをHTMLに組み込んで完了です。
<!-- index.html --> <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>Rail5 + React Todo</title> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7" crossorigin="anonymous"> </head> <body> <div id="todos" class="container"></div> <script type="text/javascript" src="dist/bundle.js"></script> </body> </html>
ReactはあくまでUIに特化したライブラリなので、正直Ajax周りの処理をどこに書いてどうしたらいいかってのが全然わかりませんでした。
これをRedux化するとどうなるんでしょうかね。続きは次回。
入門 React ―コンポーネントベースのWebフロントエンド開発
- 作者: Frankie Bagnardi,Jonathan Beebe,Richard Feldman,Tom Hallett,Simon HØjberg,Karl Mikkelsen,宮崎空
- 出版社/メーカー: オライリージャパン
- 発売日: 2015/04/03
- メディア: 大型本
- この商品を含むブログ (2件) を見る