Rails APIモードとReact Hooksを使ってToDoリストを作る

# 目的
React hooksの学習、API通信について学習のために作成。
自己の理解を深めるために、記事にする。
# 前提
Railsの立ち上げまでは、駆け足で割愛します。
学習中の身のため、間違いがある可能性大
もし、目に止まればご指摘いただけると幸いです。

# アウトライン
1. Rails APIを作る
2. Reactの開発環境を整える
3. Createを作る
4. Readを作る
5. updateを作る
6. Deleteを作る

# 環境
Rails 6.0.3.4
React 17.0.1

# Rails APIを作る
## Rails APIを立ち上げる
```terminal:console
$ rails new app -d mysql --api
$ cd app
```

Gemfileの`Rack-cors`のコメントアウトを解除する

```
$ bundle install
$ rails g model issue name:text
$ rake db:create
$ rake db:migrate
```
適当にseedsしちゃいます。

```ruby:seeds.rb
Issue.create([
  {name: "点"},
  {name: "線"},
  {name: "面"}
])
```
```terminal:console
$ rake db:seed
```
## コントローラを作る
```terminal:console
$ rails g controller issues
```

`issues_controller.rb`を下記のように書きます。
Json形式で返してあげるように書きます。

```ruby:issues_controller.rb
class IssuesController < ApplicationController

  def index
    @issue = Issue.all
    render json: @issue
  end

  def create
    @issue = Issue.create(name: params[:name])
    render json: @issue
  end

  def update
    @issue = Issue.find(params[:id])
    @issue.update_attributes(name: params[:name])
    render json: @issue
  end

  def destroy
    @issue = Issue.find(params[:id])
    if @issue.destroy
      head :no_content, status: :ok
    else
      render json: @issue.errors, status: :unprocessable_entity
    end
  end
end
```
`routes.rb`を整えて

```ruby:routes.rb
Rails.application.routes.draw do
  resources :issues
end
```
## railsを立ち上げてJSONが返ってくるか確認

```terminal
$ rails s -p 3001
```
下記のどちらでもいいのでJSONが返ってきていることを確認する
* コンソールで`curl -g localhost:3001/issues/`
* ブラウザで`localhost:3001/issues/`

```
[{"id":1,"name":"点","created_at":"2020-12-12T07:50:20.293Z","updated_at":"2020-12-12T07:50:20.293Z"},{"id":2,"name":"線","created_at":"2020-12-12T07:50:20.298Z","updated_at":"2020-12-12T07:50:20.298Z"},{"id":3,"name":"面","created_at":"2020-12-12T07:50:20.302Z","updated_at":"2020-12-12T07:50:20.302Z"}]
```

このあと、Rails側3000番ポートに、Reactがアクセスするので、3000番ポートへのアクセスを許可します。`application.rb`に下記を追記します。

```ruby:application.rb
・
・
・
  config.api_only = true
  config.middleware.insert_before 0, Rack::Cors do
    allow do
      origins 'http://localhost:3000'
      resource '*',
      :headers => :any,
      :methods => [:get, :post, :patch, :delete, :options]
    end
  end
```
Rails側の設定はこれで終わり！

# React側の開発環境を整える
## react-create-appを使う
下記コマンドを使用して、「react-create-app」を使えるようにする

```
$ npm install -g create-react-app
```

`appディレクトリ`上で下記コマンドを実行する
todo_frontというディレクトリと付随するものが作成される

```
$ create-react-app todo_front
```

下記のようなディレクトリ構成にするために、componentsディレクトリとTodo.jsを作成する

```
.
├── App.css
├── App.js
├── App.test.js
├── components
│   └── Todo.js
├── index.css
├── index.js
├── logo.svg
└── registerServiceWorker.js
```

`App.js`を下記のように書き換え、componentsディレクトリ内の`Todo.js`を読み込むようにする
（このあと、拡張していこうと思いまして...）

`Todo.js`にRailsと通信するAjax部分を記述していく。
今回はAPIと通信する便利なライブラリaxiosを使用する。

ついでに、cssフレームワークに`material-ui`もインストールする

```terminal
$ npm install axios
$ npm install @material-ui/core
```

### 先に完成したコードを記載

```react:App.js
import React from 'react';
import Todo from './components/Todo'
import './App.css';

export default function App() {
  return (
    <div className="App">
      <Todo/>
    </div>
  )
}
```

```react:components/Todo.js
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import {
  Button,
  Container,
  CssBaseline,
  List,
  ListItem,
  ListItemText,
  Input,
  ListItemSecondaryAction,
  Checkbox
  } from '@material-ui/core';

export default function MainContainer ()  {
  const [createissue, setCreateissue] = useState("");
  const [issues, setIssues] = useState([]);
  const [updateissue, setUpdateissue] = useState("");

  const createIssue = (event) => {
    console.log("イベント発火")
    axios.post('http://localhost:3001/issues',
      {
        name: createissue 
      }
    ).then(response => {
      console.log("registration response", response.data)
      setIssues([...issues, {
        id: response.data.id,
        name: response.data.name
      }])
      resetTextField()
    }).catch(error => {
      console.log("registration error", error)
    }).catch(data =>  {
      console.log(data)
    })
    event.preventDefault()
  }

  useEffect(()  =>  {
    async function fetchData()  {
      const result = await axios.get('http://localhost:3001/issues',)
        console.log(result)
        console.log(result.data)
        setIssues(result.data);
      }
      fetchData();
      }, []);

  const deleteIssue = (id) => {
    axios.delete(`http://localhost:3001/issues/${id}`)
    .then(response => {
      setIssues(issues.filter(x => x.id !== id))
      console.log("set")
    }).catch(data =>  {
      console.log(data)
    })
  }

  const updateIssue = (id) => {
    axios.patch(`http://localhost:3001/issues/${id}`,
    { 
      name: updateissue
    }
    ).then(response => {
      setIssues(issues.filter(x => x.id !== id))
      console.log(response.data)
    }).catch(data =>  {
      console.log(data)
    })
  }

  const resetTextField = () => {
    setCreateissue('')
  }
  const handleUpdate = (event) => {
    setUpdateissue(event.target.value)
  }

  return (
    <React.Fragment>
      <Container component='main' maxWidth='xs'>
        <CssBaseline/>
          <form onSubmit={createIssue}>
            <Input
                type="text"
                name="issue"
                value={createissue}
                placeholder="Enter text"
                onChange={event => setCreateissue(event.target.value)}
            />
            <Button
              type="submit"
              variant='contained'
              color='primary'>
                つぶやく
            </Button>
          </form>
        <List
          style={{marginTop: '48px'}}
          component='ul'
        >
          {issues.map(item => (
            <ListItem key={item.id} component='li' >
              <Checkbox
                value='primary'
                onChange={() => deleteIssue(item.id)}
              />
              <ListItemText>
                ID:[{item.id}]
                Name:[{item.name}]
              </ListItemText>
              <ListItemSecondaryAction>
                <form>
                  <Input
                    type="text"
                    name="issue"
                    value={updateissue} 
                    onChange={event => handleUpdate(event)}
                  />
                  <Button
                    type="submit"
                    onClick={() => updateIssue(item.id)}
                  >
                    更新
                  </Button>
                </form>
              </ListItemSecondaryAction>              
            </ListItem>
          ))}
        </List>
      </Container>
    </React.Fragment>
  );
}
```

# Createを作る
### 新しいissueを作成するフォームを作る
useStateを使用して、フォームで入力したデータをcreateissueとして一時保持させる

```react
  const [createissue, setCreateissue] = useState("");
```
インプットフォームに入力するとvalueがデータを持ちます。
そのデータを保持させるために、setStateで作った`createissue`を使います。
そして、「つぶやく」ボタンを押すとonSubmitで`createIssue`が発火します。

```react
          <form onSubmit={createIssue}>
            <Input
                type="text"
                name="issue"
                value={createissue}
                placeholder="Enter text"
                onChange={event => setCreateissue(event.target.value)}
            />
            <Button
              type="submit"
              variant='contained'
              color='primary'>
                つぶやく
            </Button>
          </form>
```


### 作成した新しいissueをaxios経由でDBへ書き込む
`createIssue`が発火するとlocalhost:3001/issuesにpostします！（そのまんま）
issueのnameに先ほど入力し保持した`createissue`として渡します。
無事、postに成功したらresponseにデータを保持させて返ってきます。
そして、インプットフォームを空欄に戻すために`resetTextField`を組んで、setCreateissueを空にします。
axiosについてはaxiosを調べた方が詳しいので割愛！

```react
const createIssue = (event) => {
    console.log("イベント発火")
    axios.post('http://localhost:3001/issues',
      {
        name: createissue 
      }
    ).then(response => {
      console.log("registration response", response.data)
      setIssues([...issues, {
        id: response.data.id,
        name: response.data.name
      }])
      resetTextField()
    }).catch(error => {
      console.log("registration error", error)
    }).catch(data =>  {
      console.log(data)
    })
    event.preventDefault()
  }

  const resetTextField = () => {
    setCreateissue('')
  }
```

# Readを作る
すでにCreateに登場したが、useStateを使ってissuesにReactで表示するデータを保持させる。

```react
  const [issues, setIssues] = useState([]);
```
useEffectを使ってレンダーされる度に、getを飛ばすように設定する。
そして、第2引数に[]を渡して再度レンダリングされないようにする
> ここら辺の理解が曖昧


```
  useEffect(()  =>  {
    async function fetchData()  {
      const result = await axios.get('http://localhost:3001/issues',)
        console.log(result)
        console.log(result.data)
        setIssues(result.data);
      }
      fetchData();
      }, []);
```
mapを使って、issues内にある

```react
          {issues.map(item => (
            <ListItem key={item.id} component='li' >
              <ListItemText>
                ID:[{item.id}]
                Name:[{item.name}]
              </ListItemText>
```

# Updateを作る
要領はCreateと同じ。
updateIssue関数に「更新したいid」と「入力したvalue」を渡す

```react
  const [updateissue, setUpdateissue] = useState("");
```

```react
              <ListItemSecondaryAction>
                <form>
                  <Input
                    type="text"
                    name="issue"
                    value={updateissue} 
                    onChange={event => handleUpdate(event)}
                  />
                  <Button
                    type="submit"
                    onClick={() => updateIssue(item.id)}
                  >
                    更新
                  </Button>
                </form>
              </ListItemSecondaryAction>              
```
渡されたidのissueのnameを入力したupdateissueに変更するよう、railsへ送る

```react
  const updateIssue = (id) => {
    axios.patch(`http://localhost:3001/issues/${id}`,
    { 
      name: updateissue
    }
    ).then(response => {
      setIssues(issues.filter(x => x.id !== id))
      console.log(response.data)
    }).catch(data =>  {
      console.log(data)
    })
  }
```

# Deleteを作る
updateの要領と大体同じですが、
私の場合はチェックボックスにチェックを入れたら、deleteIssueが発火するようにしています。
発火したらIdとdeleteがRailsに送られ、削除するようになっています。
削除が成功したらの信号が返ってきたら、フロント側の表示リストを書き換えます。


```react

              <Checkbox
                value='primary'
                onChange={() => deleteIssue(item.id)}
              />
```

```react
  const deleteIssue = (id) => {
    axios.delete(`http://localhost:3001/issues/${id}`)
    .then(response => {
      setIssues(issues.filter(x => x.id !== id))
      console.log("set")
    }).catch(data =>  {
      console.log(data)
    })
  }
```

# 最後に
参考にさせてもらった記事をベースにHooksへ書き換えてみました。
しかし、1つのコンポーネントへ冗長に書いてしまったので、他のhooksの学習含めてForm, Viewとかにコンポーネント分けをやっていこうと思います。
でき上がったらまた別の記事にします。

![ezgif.com-gif-maker.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/397356/2aea58d7-dc6b-2e90-74be-60c309bd2e75.gif)

## 参考にした記事
https://qiita.com/yoshimo123/items/9aa8dae1d40d523d7e5d
https://ja.reactjs.org/docs/hooks-overview.html