railsとreactでtodoアプリを作る！（part3）

# 作成するアプリについて
railsとreactを用いたtodoアプリになります。
https://todo-app-rails-react.herokuapp.com/todoes

https://github.com/taiki-nd/todo-app

（part1）

https://qiita.com/taiki-nd/items/aa9d0a03d1950876efab

（part2）

https://qiita.com/taiki-nd/items/41230829bcedd73bdec4

（part4）

https://qiita.com/taiki-nd/items/ccd07c79a2eca0dba716

（part5）

https://qiita.com/taiki-nd/items/b7b9a6a24f0c99458b2a

# 開発環境
* ruby3.0
* rails6.1(rails6以上必須)
* react17.0.2
* vscode

# アプリの作成手順

1. サーバーサイドの実装
    1. アプリの作成
    1. turbolinksの無効化
    1. モデル＆テーブルの作成
    1. 初期データの作成
    1. top#indexの作成
    1. todoes_controllerの作成
    1. ルーティングの設定
    1. application_controllerの設定
    1. application.html.erbに追記(part1でここまで完了済み)
1. フロントサイドの実装
    1. componentsフォルダの作成
    1. リセットcssと共通cssの設定
    1. index.jsxの記述
    1. フロントの実装準備
    1. App.jsの記述(part2でここまで完了済み)
    1. TodoList.jsの記述
    1. NewTodo.jsの記述
    1. EditTodo.jsの記述

早速ですが進めていきましょう！

# アプリ作成
## 2. フロントサイドの実装
### 2.6 TodoList.jsの記述
```jsx:app/javascript/components/Todolist.js
import React from 'react'

export const TodoList = () => {
  return (
    <>
      <input />
      <button>RemoveAll</button>
      <div>
        <span>content</span>
        <button>complete</button>
        <button>edit</button>
      </div>
    </>
  )
}
```
まずはこんなところでしょうか。![スクリーンショット 2021-11-01 13.55.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1645779/711ae613-deba-ea07-d3c5-64975fecaf2e.png)

styleをあてていきます。まずは検索、全削除ボタンです

```jsx:app/javascript/components/Todolist.js
import React from 'react'
import styled from 'styled-components'

export const TodoList = () => {
  return (
    <>
      <h1>Todoes</h1>
      <InputAndRemoveAll>
        <Input />
        <RemoveAllBtn>RemoveAll</RemoveAllBtn>
      </InputAndRemoveAll>
      <div>
        <span>content</span>
        <button>complete</button>
        <button>edit</button>
      </div>
    </>
  )
}

const InputAndRemoveAll = styled.div`
  display: flex;
  justify-content: space-between;
  align-items: center;
`

const Input = styled.input`
  font-size: 20px;
  width: 100%;
  height: 40px;
  margin: 10px 0;
  padding: 10px;
`

const RemoveAllBtn = styled.button`
  width: fit-content;
  height: 40px;
  background: #cc3366;
  border: none;
  font-weight: 500;
  margin-left: 10px;
  padding: 5px 10px;
  border-radius: 3px;
  color: #fff;
  cursor: pointer;
`
```
:::note warn
import styled from 'styled-components'を忘れずに記述してください
:::

:::note warn
h1タグはApp.cssで制御されています。
:::
![スクリーンショット 2021-11-01 14.03.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1645779/65cd49ac-be75-9534-dccf-264e6b378ddb.png)

続いてデータベースからpart1で投入した初期データを表示させます。とりあえず表示させます。

```jsx:app/javascript/components/Todolist.js
import axios from 'axios'
import React, { useState, useEffect } from 'react'
import styled from 'styled-components'

export const TodoList = () => {

  const [todoes, setTodoes] = useState([])
  //todoesにデータベース内全てのtodoが格納される

  useEffect(() => {
    axios.get('/api/v1/todoes.json')
    //todoes_controller.rbにアクセスされます。そしてtodoesがjson形式で返されるのでreact側で受け取る。
    .then(resp => {
      console.log(resp.data)
      //コンソールで何が返ってくるか確認できます。
      setTodoes(resp.data)
      //これでtodoesに全てのtodoが格納されます。
    })
  }, [])
  //第二引数にからの配列を渡すことでTodoList.jsが描画された際に一度だけuseEffectが走ります。
//以下省略
```
:::note warn
import axios from 'axios'
import React, { useState, useEffect } from 'react'
となっています。
:::
![スクリーンショット 2021-11-01 14.21.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1645779/124bf938-0b6c-461a-95e2-c24029f69f2f.png)

以上のようにconsole.log(resp.data)で初期データが``resp.data``が格納されていることが確認できます。
console.log(resp.data)はコメントアウトしておきましょう！


```jsx:app/javascript/components/Todolist.js
//以上省略
  return (
    <>
      <h1>Todoes</h1>
      <InputAndRemoveAll>
        <Input />
        <RemoveAllBtn>RemoveAll</RemoveAllBtn>
      </InputAndRemoveAll>
      <div>
        {todoes.map((val, key) => {
          //格納された全てのtodoをmapで展開していきます。
          return(
            <div key={key}>
              <span>{val.content}</span>
              <button>complete</button>
              <button>edit</button>
            </div>
          )
        })}
      </div>
    </>
  )
//以下省略
```
![スクリーンショット 2021-11-01 14.57.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1645779/ed6204f7-8968-03ab-15fa-4321edca9c67.png)

seeds.rbで投入したデータが表示されました！


```jsx:app/javascript/components/Todolist.js
  return (
    <>
      <h1>Todoes</h1>
      <InputAndRemoveAll>
        <Input />
        <RemoveAllBtn>RemoveAll</RemoveAllBtn>
      </InputAndRemoveAll>
      <div>
        {todoes.map((val, key) => {
          //格納された全てのtodoをmapで展開していきます。
          return(
            <List key={key}>
              <TodoContent>{val.content}</TodoContent>
              <Btns>
                <Btn>complete</Btn>
                <Btn>edit</Btn>
              </Btns>
            </List>
          )
        })}
      </div>
    </>
  )
}

const InputAndRemoveAll = styled.div`
  display: flex;
  justify-content: space-between;
  align-items: center;
`

const Input = styled.input`
  font-size: 20px;
  width: 100%;
  height: 40px;
  margin: 10px 0;
  padding: 10px;
`

const RemoveAllBtn = styled.button`
  width: fit-content;
  height: 40px;
  background: #cc3366;
  border: none;
  font-weight: 500;
  margin-left: 10px;
  padding: 5px 10px;
  border-radius: 3px;
  color: #fff;
  cursor: pointer;
`

const List = styled.div`
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin: 7px auto;
  padding: 10px;
  font-size: 25px;
`

const Btns = styled.div`
  justify-content: space-around;
`

const Btn = styled.button`
  width: fit-content;
  font-size: 20px;
  padding: 0 5px;
  margin: 0 5px;
  &:hover{
    background-color: #ccffcc
  }
`

const TodoContent = styled.span`
  font-size: 24px;
`
```
cssを当てました！
![スクリーンショット 2021-11-01 16.20.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1645779/508bd59d-baeb-ea4e-2518-2674b32091f4.png)

それでは機能の方を実装していきましょう！

まずは検索機能からです！

```jsx:app/javascript/components/Todolist.js
import axios from 'axios'
import React, { useState, useEffect } from 'react'
import styled from 'styled-components'

export const TodoList = () => {

  const [todoes, setTodoes] = useState([])

  useEffect(() => {
    axios.get('/api/v1/todoes.json')
    .then(resp => {
      console.log(resp.data)
      //コンソールで何が返ってくるか確認できます。
      setTodoes(resp.data)
      //これでtodoesに全てのtodoが格納されます。
    })
  }, [])

  const [searchContent, setSearchContent] = useState('')
  //searchに関するstate

  return (
    <>
      <h1>Todoes</h1>
      <InputAndRemoveAll>
        <Input 
          type="text"
          placeholder="search todo ..."
          onChange={e => {
            setSearchContent(e.target.value)
          }} />
        <RemoveAllBtn>RemoveAll</RemoveAllBtn>
      </InputAndRemoveAll>
      <div>
        {todoes.filter((val) => {
          if(searchContent === ""){
            return val
          }else if(val.content.toLowerCase().includes(searchContent.toLowerCase())){
            //検索ワードに対して検索されるようにフィルターにかける。
            return val
          }
          //filterにかけたtodoに対してmapで展開していく。
        }).map((val, key) => {
          return(
            <List key={key}>
              <TodoContent>{val.content}</TodoContent>
              <Btns>
                <Btn>complete</Btn>
                <Btn>edit</Btn>
              </Btns>
            </List>
          )
        })}
      </div>
    </>
  )
}

//以下省略
```
これで検索ができます。![スクリーンショット 2021-11-01 16.45.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1645779/276c4f33-829c-ee57-bee8-d8b9f793ad8f.png)

続いてRemovwAllの実装に移ります。

```jsx:app/javascript/components/Todolist.js
//以上省略
export const TodoList = () => {

  const [todoes, setTodoes] = useState([])

  useEffect(() => {
    axios.get('/api/v1/todoes.json')
    .then(resp => {
      console.log(resp.data)
      setTodoes(resp.data)
    })
  }, [])

  const [searchContent, setSearchContent] = useState('')

  const onClickRemoveAllBtn = () => {
    const confirm = window.confirm('Do you really want to delete all TodoLists?')
    //いきなり削除できないように確認ダイアログを表示させます。
    if(confirm){
      axios.delete('/api/v1/todoes/destroy_all')
      //todoes_controllerのdestroy_allを選択する
      .then(resp => {
        setTodoes([])
        //setTodoesをからの配列に更新することで表示を無くします。
      })
    }
  }

  return (
    <>
      <h1>Todoes</h1>
      <InputAndRemoveAll>
        <Input 
          type="text"
          placeholder="search todo ..."
          onChange={e => {
            setSearchContent(e.target.value)
          }} />
        <RemoveAllBtn onClick={onClickRemoveAllBtn} >RemoveAll</RemoveAllBtn>
         //onClickRemoveAllBtnを呼び出す
      </InputAndRemoveAll>
      <div>
        {todoes.filter((val) => {
          if(searchContent === ""){
//以下省略
```
ボタンをクリックすると綺麗さっぱり削除されます！
![スクリーンショット 2021-11-01 17.02.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1645779/7dca409e-7fce-0c44-2295-9c425a174aac.png)

ターミナルで``rails db:seed``を実行してデータを戻しておいてください。

最後に完了ボタンの実装をします。

```jsx:app/javascript/components/Todolist.js
//以上省略

  const onClickRemoveAllBtn = () => {
    const confirm = window.confirm('Do you really want to delete all TodoLists?')
    if(confirm){
      axios.delete('/api/v1/todoes/destroy_all')
      .then(resp => {
        setTodoes([])
      })
    }
  }

  const onClickCompleteBtn = (index, val) => {
    //valは選択されたtodoの情報が格納されています
    const val2 = {
      id: val.id,
      content: val.content,
      complete: !val.complete
      //completeカラムをひっくり返します（boolean型なのでtrueならfalseに、falseならtrueに）
    }
    axios.patch(`/api/v1/todoes/${val.id}`, val2)
    .then(resp => {
      const BrandNewTodoes = [...todoes]
      BrandNewTodoes[index].complete = resp.data.complete
      //completeボタンを押されたtodoを選んで、レスポンスで返ってきたcompleteカラムを代入します
      setTodoes(BrandNewTodoes)
      //これで表示がかわります
    })
  }

  return (
    <>
      <h1>Todoes</h1>
      <InputAndRemoveAll>
        <Input 
          type="text"
          placeholder="search todo ..."
          onChange={e => {
            setSearchContent(e.target.value)
          }} />
        <RemoveAllBtn onClick={onClickRemoveAllBtn} >RemoveAll</RemoveAllBtn>
      </InputAndRemoveAll>
      <div>
        {todoes.filter((val) => {
          if(searchContent === ""){
            return val
          }else if(val.content.toLowerCase().includes(searchContent.toLowerCase())){
            return val
          }
        }).map((val, key) => {
          return(
            <List key={key}>
              <TodoContent complete={val.complete}>{val.content}</TodoContent>
              <Btns>
                {val.complete ? (
                  <Btn onClick={() => onClickCompleteBtn(key, val)} >incomplete</Btn>
                ) : (
                  <Btn onClick={() => onClickCompleteBtn(key, val)} >complete</Btn>
                )}
                <Btn>edit</Btn>
              </Btns>
            </List>
          )
        })}
      </div>
    </>
  )
}

//中略

const TodoContent = styled.span`
  font-size: 24px;
  ${({ complete }) => complete && `
    opacity: 0.4;
  `}
`
```

以下のように表示されるかと思います！

![スクリーンショット 2021-11-01 17.33.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1645779/df4243d0-22e6-823d-ef00-e6fba03d8de4.png)

長くなってしまったのでここまでにしておきましょう！お疲れ様でした！次回に続きます！