Rails×ReactでRailsのscaffoldで生成されるような簡単なcrudができるアプリケーションを実装したので、備忘録として残しておこうと思います。
###アプリケーションの概要
タイトルと本文のあるメッセージをCRUD+絞り込み検索するアプリケーション
やったこと
(1) 環境の構築
(2) メッセージ一覧を表示する。
(3) 新規メッセージを作成する。(本エントリ)
(4) メッセージ詳細の表示、編集、更新をする。
(5) メッセージを削除する、メッセージを検索する。
###やってないこと
- Redux、MobXのステート管理のためのフレームワークの導入
- テストの実装
- CSSの実装
- セキュリティやデータの整合性を確認する実装
- 用語や実装の解説
用語や実装の解説については、参考にさせていただいたサイトのリンクを各実装毎に貼っておりますので、そちらをご確認下さい。
参考サイトの作成者の皆様ありがとうございます。
【ご一読いただくにあたって】
・間違いがありましたらコメントにて教えて下さい。(まだまだ勉強中です。)
新規登録のフローは以下のような流れをイメージしています。
- [New Message] をクリックすると新規登録画面が表示される。
- 新規登録画面では、メッセージのタイトルと詳細を入力するフォームが表示されている。
- [submit]をクリックするとメッセージが登録されて、一覧が表示される。
- 一覧画面では 3.で登録したメッセージのタイトルが表示されている。
画面 | URL |
---|---|
一覧画面 | http://localhost:4000/ |
新規登録画面 | http://localhost:4000/new |
###React
[New Message]をクリックとNew.js
のNewコンポーネントが表示されるようにする
New.jsを作成
touch New.js
import React, { Component } from 'react';
class New extends Component {
render() {
return (
<div>
<p>New Message</p>
<p>New Message Form</p>
</div>
);
}
}
export default New;
ルーティングを設定
ReactはSPAなので、URLを変えずに新規登録画面(Newコンポーネント)を表示することも可能だと思うのですが、今回は新規登録画面(Newコンポーネント)のURLをhttp://localhost:4000/new
というように設定したいと思います。
react-routerのインストール
yarn add react-router react-router-dom
App.jsにルーティングを設定
import React, { Component } from 'react';
// react-router-dom をインポートする
import { BrowserRouter as Router, Link, Switch, Route } from 'react-router-dom';
import List from './List';
import New from './New';
const App = () => (
// <Router>でAppコンポーネントをラップする
<Router>
<div>
<AppHeader />
// 現在のURLが path と一致した場合に component= でセットしているコンポーネントをレンダリングする
<Switch>
// exact によって、path の記述と一致するURLの場合にのみレンダリングする
// exact がないと /new/example という path があったとしても /new の方がレンダリングされてしまう
<Route exact path='/' component={ List }/>
<Route exact path='/new' component={ New } />
</Switch>
<AppFooter />
</div>
</Router>
)
const AppHeader = () => (
<div>
<h3>Message App</h3>
<p>
// Link to で a が生成される
<Link to="/">[Home]</Link>
<Link to="/new">[New Message]</Link>
</p>
</div>
)
const AppFooter = () => (
<div>
<p>
Message App Footer
</p>
</div>
)
export default App
[Home]をクリックするとURLが '/' となりListコンポーネントがレンダリングされて、
[New Message]をクリックするとURLが '/new' となり、Newコンポーネントがレンダリングされます。
コンポーネントは以下のようなイメージです。
新規登録画面でメッセージのタイトルと詳細を入力するフォームが表示されるようにする
入力フォームを実装
次にNew.jsにメッセージの入力フォームを実装していきます。
import React, { Component } from 'react';
class New extends Component {
render() {
return (
<div>
<p>New Message</p>
<p>New Message Form</p>
</div>
);
}
}
export default New;
を以下のように修正します。
import React, { Component } from 'react';
class New extends Component {
// コンポーネントの初期化 インスタンスを作成したタイミングで実行される
constructor(props){
// super(props)を呼び出すことで、 this を使用できる
super(props);
// Message の title と content を this.state にセットする
this.state = {
title: '',
content: ''
};
}
render() {
// const にセットすることで、 value={ this.state.title } と書かなくて済む
const { title, content } = this.state;
return (
// return では1つのコンポーネントしか返すことが出来ない
// return (
// <div>...</div>
// <div>...</div>
// );
// ↑はエラーとなる
<div>
<p>New Message</p>
<div>
<form>
<label>title:</label>
<input type="text" name="title" value={ title } />
<label>content:</label>
<textarea name="content" value={ content } />
<input type="submit" value="Submit" />
</form>
</div>
</div>
);
}
}
export default New;
constructor
や super(props)
の詳細は、公式リファレンスを確認するといいかもです。
これで入力フォームが表示されたと思いますので、localhost:4000
を確認してみましょう。(イメージは省略)
しかし、今のままだと title に文字を入力しても、文字がinputタグに反映されないと思います。
文字を反映させるには inputタグ や textareaタグ に onChangeイベント を実装し、 setState を更新しなければなりません。
import React, { Component } from 'react';
class New extends Component {
constructor(props){
super(props);
this.state = {
title: '',
content: ''
};
// this を bindする
this.handleInputValue = this.handleInputValue.bind(this);
}
// handleInputValue を実装
handleInputValue(event){
const field = event.target.name;
this.state[field] = event.target.value;
// setState することでレンダリングされる
this.setState({
[field]: this.state[field]
});
}
render() {
const { title, content } = this.state;
return (
<div>
<p>New Message</p>
<div>
<form>
<label>title:</label>
// onChange で handleInputValue を発火させる
<input type="text" name="title" value={ title } onChange={ this.handleInputValue } />
<label>content:</label>
<textarea name="content" value={ content } onChange={ this.handleInputValue } />
<input type="submit" value="Submit" />
</form>
</div>
</div>
);
}
}
export default New;
アロー関数
handleInputValue のような関数が増えていく度に constructor でbindするのは手間です。
アロー関数を使うことでその手間を削減することができるので、アロー関数を使えるように設定したいと思います。
babel-preset-stage-0
をインストールします。
yarn add babel-preset-stage-0
.babelrc
を以下のように編集します。
{
"presets": [
"es2015", "react", "stage-0" # "stage-0"を追加
]
}
これでアロー関数を使えるようになったので、 New.js
を以下の様に修正します。
import React, { Component } from 'react';
class New extends Component {
constructor(props){
super(props);
this.state = {
title: '',
content: ''
};
// this.handleInputValue = this.handleInputValue.bind(this);
}
//handleInputValue(event){
handleInputValue = (event) => {
const field = event.target.name;
this.state[field] = event.target.value;
this.setState({
[field]: this.state[field]
});
}
render() {
const { title, content } = this.state;
return (
<div>
<p>New Message</p>
<div>
<form>
<label>title:</label>
<input type="text" name="title" value={ title } onChange={ this.handleInputValue } />
<label>content:</label>
<textarea name="content" value={ content } onChange={ this.handleInputValue } />
<input type="submit" value="Submit" />
</form>
</div>
</div>
);
}
}
export default New;
[submit]をクリックするとメッセージが登録されて、一覧が表示される
それでは次に [submit]かクリックされてメッセージが登録されるまでを実装します。
handleSubmitを実装
submitがクリックされた時のイベントを実装します。
import React, { Component } from 'react';
// Request を送るURLを指定(RailsのAPIサーバ)
const REQUEST_URL = 'http://localhost:3000/messages.json'
class New extends Component {
constructor(props){
super(props);
this.state = {
title: '',
content: ''
};
}
handleInputValue = (event) => {
const field = event.target.name;
this.state[field] = event.target.value;
this.setState({
[field]: this.state[field]
});
}
// submitがクリックされた時の処理
handleSubmit = (event) => {
// デフォルトの遷移を抑制する
event.preventDefault();
// $.ajax()に代替する fetch
// 結果がPromiseで返される
fetch(REQUEST_URL, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
// JSON.stringifyで JSON 文字列に変換する
body: JSON.stringify({
message: {
title: this.state.title,
content: this.state.content
}
})
})
.then((response) => {
if (!response.ok) {
throw Error(response.statusText);
}
// 一覧コンポーネントを表示する
this.props.history.push('/');
})
.catch((err) => {
console.error(err);
});
}
render() {
const { title, content } = this.state;
return (
<div>
<p>New Message</p>
<div>
<form onSubmit={ this.handleSubmit }>
<label>title:</label>
<input type="text" name="title" value={ title } onChange={ this.handleInputValue } />
<label>content:</label>
<textarea name="content" value={ content } onChange={ this.handleInputValue } />
<input type="submit" value="Submit" />
</form>
</div>
</div>
);
}
}
export default New;
- Fetch API 解説、または Web において "Fetch する" とは何か?
- jQuery.ajax()の代替としてFetch APIをざっくり使ってみる
- React Routerのhistoryはどこから来るのか
- Response
まだRails側を実装していないので、登録は出来ませんが、localhost:4000
で挙動を確認します。
(スクリーンショットの動画機能の精度が悪いせいだと思うのですが、レンダリングが遅く見えますが、実際にはこんなに遅くないと思います。実際遅くないです。)
Rails
メッセージの登録(APIサーバ)
今回の記事のメインはReact側なので、Rails側の解説は省略したいと思います。
class MessagesController < ApplicationController
def index
messages = Message.all
render json: messages
end
def create
message = Message.new(message_params)
message.save!
end
private
def message_params
params.require(:message).permit(:title, :content)
end
end
Rails.application.routes.draw do
resources :messages, only: [:index, :create], format: 'json'
# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
end
Rails側の実装を完了したら、localhost:4000
を確認してみます。
以下のような挙動になっていれば成功です。
次は、メッセージの詳細/編集/更新を実装してみたいと思います。こちら