Rails×ReactでRailsのscaffoldで生成されるような簡単なcrudができるアプリケーションを実装したので、備忘録として残しておこうと思います。
###アプリケーションの概要
タイトルと本文のあるメッセージをCRUD+絞り込み検索するアプリケーション
やったこと
(1) 環境の構築
(2) メッセージ一覧を表示する。
(3) 新規メッセージを作成する。
(4) メッセージ詳細の表示、編集、更新をする。(本エントリ)
(5) メッセージを削除する、メッセージを検索する。
###やってないこと
- Redux、MobXのステート管理のためのフレームワークの導入
- テストの実装
- CSSの実装
- セキュリティやデータの整合性を確認する実装
- 用語や実装の解説
用語や実装の解説については、参考にさせていただいたサイトのリンクを各実装毎に貼っておりますので、そちらをご確認下さい。
参考サイトの作成者の皆様ありがとうございます。
【ご一読いただくにあたって】
・間違いがありましたらコメントにて教えて下さい。(まだまだ勉強中です。)
##事前準備
一覧表示をscaffoldに寄せる
Railsのscaffoldで生成される一覧の表示は以下のようなものです。
List.js
の表示をこれに寄せるために以下のように修正します。
~ render内のみ修正なので上は省略します ~
render(){
const { messages } = this.state;
return(
<div>
// tableタグを追加する
<table>
<thead>
<tr>
<th>title</th>
<th>content</th>
<th></th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
{ messages.map((message) => {
return (
<tr key={ message.id }>
<td>{ message.title }</td>
<td>{ message.content }</td>
<td>show</td>
<td>edit</td>
<td>destroy</td>
</tr>
);
})}
</tbody>
</table>
</div>
);
}
}
export default List;
そうすると表示は以下のようになったと思います。
若干違いますが、これで進めていきます。
##メッセージの詳細
詳細画面まわりのフローは以下のような流れをイメージしています。
- [show] をクリックするとメッセージのタイトルと詳細が表示される。(詳細画面)
- 詳細画面には[Edit]があり、クリックすると編集画面が表示される。
画面 | URL |
---|---|
詳細画面 | http://localhost:4000/message/id |
###React
[show] をクリックするとメッセージのタイトルと詳細を表示する
まずは、ルーティングを確認するためにShow.js
を作成します。
Show.jsを作成
import React, { Component } from 'react';
class Show extends Component {
render() {
return (
<div>
<p>Show Message</p>
</div>
);
}
}
export default Show;
ルーティングを設定
次に、ルーティングを設定します。
今回は、新規登録画面の時と違い、id
をキーにして、詳細画面を表示するので、パラメータにid
をセットします。
App.js
とList.js
を以下のように修正します。
~ 他のimportは省略します。 ~
import Show from './Show'; // Showコンポーネントのインポートを追加
const App = () => (
<Router>
<div>
<AppHeader />
<Switch>
<Route exact path='/new' component={ New } />
// pathが '/message/:id' のときにShowコンポーネントが表示されるようにします。
<Route exact path='/message/:id' component={ Show }/>
<Route exact path='/' component={ List }/>
</Switch>
<AppFooter />
</div>
</Router>
)
この時点でlocalhost:4000
を確認してみます。(イメージは省略します。)
URLにidが渡されていて、Show Message
が表示されていれば成功です。
idからデータを取得
Rails(APIサーバ)側でのデータの取得
それでは、次にRails(APIサーバ)を修正してidが渡ってきたときに、該当するメッセージだけをjsonで返すようにします。
今回の記事のメインはReact側なので、Rails側の解説は省略したいと思います。
Rails.application.routes.draw do
resources :messages, only: [:index, :create, :show], format: 'json'
# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
end
class MessagesController < ApplicationController
before_action :set_message, only: [:show]
def index
messages = Message.all
render json: messages
end
def create
message = Message.new(message_params)
message.save!
end
def show
render json: @message
end
private
def set_message
@message = Message.find(params[:id])
end
def message_params
params.require(:message).permit(:title, :content)
end
end
http://localhost:3000/messages/1
をURLに入力したときにjson
でMessage.id
が 1 のデータが返ってくることを確認します。
React側でのデータの受け取り
次にReact側での実装です。
Show.js
を、パラメータでidが渡ってきたら、「(1)取得する/(2)Railsに渡す/(3)Railsからjsonを受けるとる/(4)受け取ったjsonデータを表示する」というように修正します。
import React, { Component } from 'react';
import { Link } from 'react-router-dom';
// RailsAPIのURL
const REQUEST_URL_SHOW = 'http://localhost:3000/messages/';
class Show extends Component {
constructor(props) {
super(props)
// this.props.match.params.idでパラメータのidを取得する
const message_id = parseInt(this.props.match.params.id, 10) // (1)
// message_idをstateにセットする
this.state = {
id: message_id,
message: ''
};
}
// New.jsと同様にrenderされる前にfetchDataを走らせる
componentWillMount() {
this.fetchData()
}
// RailsAPIをたたく
fetchData() {
fetch(REQUEST_URL_SHOW + this.state.id) // (2)
.then((response) => {
if (!response.ok) {
throw Error(response.statusText);
}
return response;
})
.then((response) => response.json()) // (3) jsonへパースする
// 返ってきたデータを this.state.message にセットする
// setState することでレンダリングされる
.then((responseData) => {
this.setState({
message: responseData // (4)
})
})
.catch((err) => {
console.error(err);
});
}
render(){
const { message } = this.state;
return(
<div key={message.id}>
<div>
<b>Title</b>
<div>{ message.title }</div>
</div>
<div>
<b>Content</b>
<div>{ message.content }</div>
</div>
// 編集画面へのリンク ( リンク先はこの段階では未設定 )
<div><Link to={ '/message/' + message.id + /edit' }>edit</Link></div>
</div>
);
}
}
export default Show;
それでは挙動を確認します。
(gifではレンダリングの挙動がカクカクしていますが、ブラウザ上ではカクカクしていないと思います...)
以上で、詳細画面の表示は完了です。
続けて、メッセージの編集/更新を実装していきます。
##メッセージの編集/更新
編集/更新のフローは以下のような流れをイメージしています。
- [Edit] をクリックすると編集フォームが表示される。
- [Update] をクリックするとメッセージが更新され、一覧画面が表示される。
画面 | URL |
---|---|
編集画面 | http://localhost:4000/message/:id/edit |
###React
[Edit]をクリックとEdit.js
のEditコンポーネントが表示されるようにする
Edit.jsを作成
Edit.js
ではShow.js
と同様にパラメータでid
を渡して、「(1)取得する/(2)Railsに渡す/(3)Railsからjsonを受けるとる/(4)受け取ったjsonデータを表示する」というように修正します。
import React, { Component } from 'react';
const REQUEST_URL_EDIT = 'http://localhost:3000/messages/';
class Edit extends Component {
constructor(props) {
super(props)
const message_id = parseInt(this.props.match.params.id, 10)
this.state = {
id: message_id,
title: '',
content: ''
};
}
componentWillMount() {
this.fetchData()
}
fetchData() {
fetch(REQUEST_URL_EDIT + this.state.id)
.then((response) => {
if (!response.ok) {
throw Error(response.statusText);
}
return response;
})
.then((response) => response.json())
.then((responseData) => {
this.setState({
title: responseData.title,
content: responseData.content
})
})
.catch((err) => {
console.error(err);
});
}
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;
console.log(this.props.title );
return(
<div>
<p>Edit 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="Update" />
</form>
</div>
</div>
);
}
}
export default Edit;
ルーティングを設定
続けて、App.js
とList.js
を修正します。
~ 省略 ~
import Edit from './Edit';
// ReactRouter を使ったコンポーネントの定義
const App = () => (
<Router>
<div>
<AppHeader />
<Switch>
<Route exact path='/new' component={ New } />
<Route exact path='/message/:id' component={ Show }/>
// EditコンポーネントのRouteを追加する
<Route exact path='/message/:id/edit' component={ Edit }/>
<Route exact path='/' component={ List }/>
</Switch>
<AppFooter />
</div>
</Router>
)
~ 省略 ~
~ 省略 ~
render(){
const { messages } = this.state;
return(
<div>
<table>
<thead>
<tr>
<th>Title</th>
<th>Content</th>
<th></th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
{ messages.map((message) => {
return (
<tr key={ message.id }>
<td>{ message.title }</td>
<td>{ message.content }</td>
<td><Link to={ '/message/' + message.id }>Show</Link></td>
// Editへのリンクを追加する
<td><Link to={ '/message/' + message.id + '/edit' }>Edit</Link></td>
<td>destroy</td>
</tr>
);
})}
</tbody>
</table>
</div>
);
}
~ 省略 ~
ここまでで編集の機能は実装できました。
それでは次に更新の機能を実装していきます。
まずは、Edit.js
にhandleSubmit
を追加します。
~ 省略 ~
// [submit]がクリックされた時の処理を追加する
handleSubmit = (event) => {
event.preventDefault();
fetch(REQUEST_URL_EDIT + this.state.id, {
method: 'PUT',
headers: {
Accept: 'application/json',
'Content-Type': 'application/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>Edit Message</p>
<div>
<form onSubmit={ this.handleSubmit }>
~ 省略 ~
</form>
</div>
</div>
);
}
}
Rails
メッセージの更新(APIサーバ)
Rails.application.routes.draw do
resources :messages, only: [:index, :create, :show, :update], format: 'json'
# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
end
class MessagesController < ApplicationController
before_action :set_message, only: [:show, :update]
~ 省略 ~
def update
@message.update!(message_params)
end
~ 省略 ~
end
Rails側の実装を完了したら、localhost:4000
を確認してみます。
以下のような挙動になっていれば成功です。
#Form.jsの共通化
New.js
とEdit.js
で入力フォームを実装しているのですが、共通の部分が多いので共通化します。
ここでReactのprops
という概念が出てきます。
New.js
とEdit.js
をそれぞれ以下のように修正します。
import React, { Component } from 'react';
import Form from './Form';
~ 省略 ~
render() {
const { title, content } = this.state;
return (
<div>
<p>New Message</p>
<Form
submitValue='Submit'
title={ title }
content={ content }
parentSubmit={ this.handleSubmit }
parentInputValue={ this.handleInputValue } />
</div>
);
}
~ 省略 ~
import React, { Component } from 'react';
import Form from './Form';
~ 省略 ~
render(){
const { title, content } = this.state;
return(
<div>
<p>Edit Message</p>
<Form
submitValue='Update'
title={ title }
content={ content }
parentSubmit={ this.handleSubmit }
parentInputValue={ this.handleInputValue } />
</div>
);
}
~ 省略 ~
次にForm.js
を修正します。
import React, { Component } from 'react';
class Form extends Component {
constructor(props){
super(props);
}
render() {
// New.js, Edit.jsのpropsをセットします。
const { title, content, submitValue } = this.props;
return (
// this.props.xxxx という記述で親コンポーネントの関数実行します。
<div onSubmit={ this.props.parentSubmit }>
<form>
<label>title:</label>
<input type="text" name="title" value={ title } onChange={ this.props.parentInputValue } />
<label>content:</label>
<textarea name="content" value={ content } onChange={ this.props.parentInputValue } />
<input type="submit" value={ submitValue } />
</form>
</div>
);
}
}
export default Form;
挙動を確認してみます。
共通化の前と挙動が変わらなければ成功です。
次は、メッセージの削除/絞り込みを実装してみたいと思います。