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

# 作成するアプリについて
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

（part3）

https://qiita.com/taiki-nd/items/14ff0faf685905532e0f

（part4）

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

# 開発環境
* 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の記述(part3でここまで完了済み)
    1. NewTodo.jsの記述(part4でここまで完了済み)
    1. EditTodo.jsの記述

それでは進めていきましょう！

# アプリ作成
## 2. フロントサイドの実装
### 2.8 EditTodo.jsの記述
さぁ、最後の章です！頑張っていきましょう！僕も頑張って書いていきます！

まずは編集ページへのリンクを作成します！

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

export const TodoList = () => {

  const [todoes, setTodoes] =

//中略

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
          }
          //filterにかけたtodoに対してmapで展開していく。
        }).map((val, key) => {
          //格納された全てのtodoをmapで展開していきます。
          return(
            <List key={key}>
              <TodoContent complete={val.complete}>{val.content}</TodoContent>
              <Btns>
                {val.complete ? (
                  <Btn onClick={() => onClickCompleteBtn(key, val)} >complete</Btn>
                ) : (
                  <Btn onClick={() => onClickCompleteBtn(key, val)} >incomplete</Btn>
                )}
                  <Link to={`/todoes/${val.id}/edit`}>
                    <Btn>edit</Btn>
                  </Link>
                
              </Btns>
            </List>
          )
        })}
      </div>
    </>
  )
}
//以下省略
```
:::note warn
これはTodoList.jsです！
:::

:::note warn
import { Link } from 'react-router-dom'が追記されています！
編集→editに書き換えました。（実装上の問題はありません。見た目の問題です。ごめんなさい。）
またLinkタグでかこって編集ページに遷移できるように編集しています！
:::

editボタンを押して以下のページに遷移すれば成功です！
![スクリーンショット 2021-11-01 21.33.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1645779/efb68df6-3809-d74f-b47a-d27a616d369c.png)

では``EditTodo.js``の編集に移ります。
まずは以下の通りに

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

export const EditTodo = () => {
  return (
    <>
      <h1>EditTodo</h1>
      <h2>CurrentTodo</h2>
      <input 
        type="text"/>
      <button>update</button>
      <h2>CurrentStatus</h2>
      <div>
        Complete Incomplete
      </div>
      <h2>EditStatus</h2>
      <div>
        <button>complete</button>
        <button>incomplete</button>
        <button>delete</button>
      </div>
    </>
  )
}
```
ここにstyleを当てると...

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

export const EditTodo = () => {
  return (
    <>
      <h1>EditTodo</h1>
      <h2>CurrentTodo</h2>
      <EditInputAndBtn>
        <EditInput type="text" />
        <UpdateBtn>update</UpdateBtn>
      </EditInputAndBtn>
      <h2>CurrentStatus</h2>
      <Status>
        Complete Incomplete
      </Status>
      <h2>EditStatus</h2>
      <Btns>
        <CompleteBtn>complete</CompleteBtn>
        <IncompleteBtn>incomplete</IncompleteBtn>
        <DeleteBtn>delete</DeleteBtn>
      </Btns>
    </>
  )
}

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

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

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

const Status = styled.p`
  font-size: 18px;
  text-align: center;
`

const Btns = styled.div`
  width: 100%;
  display: flex;
  justify-content: space-around;
`

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

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

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

:::note warn
import styled from 'styled-components'を忘れないように気をつけてください！
:::
![スクリーンショット 2021-11-01 21.48.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1645779/6b4f175c-c111-b6f9-5ab7-458967b12f88.png)

こんな感じになっていれば成功です！

続いて機能面の実装に移ります！
まずはinputからいきます。選択したtodoのcontentの表示からいきましょう！

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

export const EditTodo = (props) => {

  const initialTodoStatus = {
    id: null,
    content: "",
    complete: false
  };

  const [currentTodo, setCurrentTodo] = useState(initialTodoStatus)

  const getTodo = id => {
    axios.get(`/api/v1/todoes/${id}`)
    //idからレスポンスされるデータを受け取り、setCurrentTodoへ
    .then(resp => {
      setCurrentTodo(resp.data);
    })
    .catch(e => {
      console.log(e);
    })
  }

  useEffect(() => {
    getTodo(props.match.params.id)
    //URLからidを取得
    console.log(props.match.params.id)//これで確認できます
  }, [props.match.params.id]);//idの変更でuseEffectが走るようにします。（editページが開いたら実行される）

  return (
    <>
      <h1>EditTodo</h1>
      <h2>CurrentTodo</h2>
      <EditInputAndBtn>
        <EditInput 
          type="text"
          value={currentTodo.content}
          content="content" />
        <UpdateBtn>update</UpdateBtn>
      </EditInputAndBtn>
      <h2>CurrentStatus</h2>
      <Status>
        Complete Incomplete
      </Status>
      <h2>EditStatus</h2>
      <Btns>
        <CompleteBtn>complete</CompleteBtn>
        <IncompleteBtn>incomplete</IncompleteBtn>
        <DeleteBtn>delete</DeleteBtn>
      </Btns>
    </>
  )
}
//以下省略
```

:::note warn
import axios from 'axios';
import React, { useEffect, useState } from 'react'
を忘れないようにしてください！
:::

![スクリーンショット 2021-11-01 22.44.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1645779/a6938a1a-cf99-8c75-531b-5fd0054c4989.png)

表示されましたね！どんどんいきます。

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

export const EditTodo = (props) => {

  const initialTodoStatus = {
    id: null,
    content: "",
    complete: false
  };

  const [currentTodo, setCurrentTodo] = useState(initialTodoStatus)

  const getTodo = id => {
    axios.get(`/api/v1/todoes/${id}`)
    .then(resp => {
      setCurrentTodo(resp.data);
    })
    .catch(e => {
      console.log(e);
    })
  }

  useEffect(() => {
    getTodo(props.match.params.id)
    //URLからidを取得
    console.log(props.match.params.id)
  }, [props.match.params.id]);

  const onChangeEditTodo = event => {
    const { name, value } = event.target;
    console.log(name)//content 
    console.log(value)//編集した文字列
    setCurrentTodo({ ...currentTodo, [name]: value });
    //todoを展開して、カラム名を指定してvalueに更新します。
  };

  const onClickUpdateTodo = () => {
    axios.patch(`/api/v1/todoes/${currentTodo.id}`, currentTodo)
    .then(resp => {
      props.history.push("/todoes");
    })
    .catch(e => {
      console.log(e);
    });
  };

  return (
    <>
      <h1>EditTodo</h1>
      <h2>CurrentTodo</h2>
      <EditInputAndBtn>
        <EditInput 
          type="text"
          value={currentTodo.content}
          name="content"
          onChange={onChangeEditTodo} />
        <UpdateBtn onClick={onClickUpdateTodo} >update</UpdateBtn>
      </EditInputAndBtn>
      <h2>CurrentStatus</h2>
      <Status>
        Complete Incomplete
      </Status>
      <h2>EditStatus</h2>
      <Btns>
        <CompleteBtn>complete</CompleteBtn>
        <IncompleteBtn>incomplete</IncompleteBtn>
        <DeleteBtn>delete</DeleteBtn>
      </Btns>
    </>
  )
}
//以下省略
```

:::note warn
EditInputタグ、UpdateBtnタグにも追記あります。
:::

以上のようにできれば
![スクリーンショット 2021-11-01 23.05.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1645779/50277325-3550-7a2a-85e6-8a63e21dcf32.png)
![スクリーンショット 2021-11-01 23.05.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1645779/fdf98ff7-e33a-4ea1-4dd9-9cc91c10110e.png)

このように編集が完了します。

続いてstatusに関してですね。

```jsx:app/javascript/components/EditTodo.js
return (
    <>
      <h1>EditTodo</h1>
      <h2>CurrentTodo</h2>
      <EditInputAndBtn>
        <EditInput 
          type="text"
          value={currentTodo.content}
          name="content"
          onChange={onChangeEditTodo} />
        <UpdateBtn onClick={onClickUpdateTodo} >update</UpdateBtn>
      </EditInputAndBtn>
      <h2>CurrentStatus</h2>
      <Status>
        {currentTodo.complete ? "Complete" : "Incomplete" }
      </Status>
      <h2>EditStatus</h2>
      <Btns>
        <CompleteBtn>complete</CompleteBtn>
        <IncompleteBtn>incomplete</IncompleteBtn>
        <DeleteBtn>delete</DeleteBtn>
      </Btns>
    </>
  )
}
```

Statusタグを編集しました。ここはcurrentTodoのcompleteカラムを確認して三項演算子で表示を変えています。

さらにボタンの実装をしていきます。ここはTodoList.jsでも同じことをやっているので合わせて確認してください。

```jsx:app/javascript/components/EditTodo.js
//以上省略
const onClickUpdateTodo = () => {
    axios.patch(`/api/v1/todoes/${currentTodo.id}`, currentTodo)
    .then(resp => {
      props.history.push("/todoes");
    })
    .catch(e => {
      console.log(e);
    });
  };

  const onClickCompleteBtn = (val) => {
    var updateVal = {
      id: val.id,
      name: val.name,
      complete: !val.complete
      //completeカラムをひっくり返します（boolean型なのでtrueならfalseに、falseならtrueに）
    };
    axios.patch(`/api/v1/todoes/${val.id}`, updateVal)
    .then(resp => {
      setCurrentTodo(resp.data);
    })
  };

  return (
    <>
      <h1>EditTodo</h1>
      <h2>CurrentTodo</h2>
      <EditInputAndBtn>
        <EditInput 
          type="text"
          value={currentTodo.content}
          name="content"
          onChange={onChangeEditTodo} />
        <UpdateBtn onClick={onClickUpdateTodo} >update</UpdateBtn>
      </EditInputAndBtn>
      <h2>CurrentStatus</h2>
      <Status>
        {currentTodo.complete ? "Complete" : "Incomplete" }
      </Status>
      <h2>EditStatus</h2>
      <Btns>
        {currentTodo.complete ? (
          <IncompleteBtn onClick={() => onClickCompleteBtn(currentTodo)}>incomplete</IncompleteBtn>
        ) : (
          <CompleteBtn onClick={() => onClickCompleteBtn(currentTodo)}>complete</CompleteBtn>
        )}
        <DeleteBtn>delete</DeleteBtn>
      </Btns>
    </>
  )
}
//以下省略
```

conpleteボタンを押して表示が変わることを確認しましょう！
![スクリーンショット 2021-11-03 16.20.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1645779/7a617c70-c6ed-6bb5-8e18-4375eda14043.png)
![スクリーンショット 2021-11-03 16.20.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1645779/af01134e-d4f9-4bae-b29a-8e74aaabe9be.png)
CurrentStatusとボタン表示がちゃんと更新されていますね！
![スクリーンショット 2021-11-03 16.21.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1645779/36e6623d-71ca-cc1e-d61d-cfc9afc12480.png)
TodoList.jsの方も更新されています！

実装箇所はあと２つです！

deleteボタンの実装をしていきましょう！

```jsx:app/javascript/components/EditTodo.js
//以上省略
const onClickCompleteBtn = (val) => {
    var updateVal = {
      id: val.id,
      name: val.name,
      complete: !val.complete
      //completeカラムをひっくり返します（boolean型なのでtrueならfalseに、falseならtrueに）
    };
    axios.patch(`/api/v1/todoes/${val.id}`, updateVal)
    .then(resp => {
      setCurrentTodo(resp.data);
    })
  };

  const onClickDeleteBtn = () => {
    const alert = window.confirm('Do you really want to delete this TodoList?')
    if (alert) {
      axios.delete(`/api/v1/todoes/${currentTodo.id}`)
      .then(resp => {
        console.log(resp.data)
        props.history.push("/todoes");
      })
      .catch(e => {
        console.log(e)
      })
    }
  }

  return (
    <>
      <h1>EditTodo</h1>
      <h2>CurrentTodo</h2>
      <EditInputAndBtn>
        <EditInput 
          type="text"
          value={currentTodo.content}
          name="content"
          onChange={onChangeEditTodo} />
        <UpdateBtn onClick={onClickUpdateTodo} >update</UpdateBtn>
      </EditInputAndBtn>
      <h2>CurrentStatus</h2>
      <Status>
        {currentTodo.complete ? "Complete" : "Incomplete" }
      </Status>
      <h2>EditStatus</h2>
      <Btns>
        {currentTodo.complete ? (
          <IncompleteBtn onClick={() => onClickCompleteBtn(currentTodo)}>incomplete</IncompleteBtn>
        ) : (
          <CompleteBtn onClick={() => onClickCompleteBtn(currentTodo)}>complete</CompleteBtn>
        )}
        <DeleteBtn onClick={onClickDeleteBtn}>削除</DeleteBtn>
      </Btns>
    </>
  )
}
//以下省略
```
ここまでくると特に難しいこともないのではと思います。

![スクリーンショット 2021-11-03 16.29.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1645779/5090b782-c00d-d8e9-a0e5-7cb5ec357604.png)
削除ボタンをクリックすると...
![スクリーンショット 2021-11-03 16.29.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1645779/7c05e818-507e-ae55-2d2c-54bb7ae334bf.png)
削除が完了しました！

最後にtoastifyの実装をして完了です！

```jsx:app/javascript/components/EditTodo.js
mport styled from 'styled-components'
import axios from 'axios';
import React, { useEffect, useState } from 'react'
import { toast } from 'react-toastify'
import "!style-loader!css-loader!react-toastify/dist/ReactToastify.css"//toastifyのcss

toast.configure();//忘れがちです

export const EditTodo = (props) => {

  const notify = () => {
    toast.success("Todo successfully updated!", {
      //NewTodo.jsをコピペするとメッセージがおかしいので修正しください。
      position: "bottom-center",
      hideProgressBar: true
    });
  }

  const initialTodoStatus = {
    id: null,
    content: "",
    complete: false
  };

  const [currentTodo, setCurrentTodo] = useState(initialTodoStatus)

  const getTodo = id => {
    axios.get(`/api/v1/todoes/${id}`)
    .then(resp => {
      setCurrentTodo(resp.data);
    })
    .catch(e => {
      console.log(e);
    })
  }

  useEffect(() => {
    getTodo(props.match.params.id)
    console.log(props.match.params.id)
  }, [props.match.params.id]);
  const onChangeEditTodo = event => {
    const { name, value } = event.target;
    console.log(name) 
    console.log(value)
    setCurrentTodo({ ...currentTodo, [name]: value });
  };

  const onClickUpdateTodo = () => {
    axios.patch(`/api/v1/todoes/${currentTodo.id}`, currentTodo)
    .then(resp => {
      notify();//ここでtoastを呼び出す
      props.history.push("/todoes");
    })
    .catch(e => {
      console.log(e);
    });
  };
//以下省略
```

:::note warn
import { toast } from 'react-toastify'
import "!style-loader!css-loader!react-toastify/dist/ReactToastify.css"//toastifyのcss
忘れずにimportしてください。
:::

:::note warn
toast.configure();を忘れがちです
:::

![スクリーンショット 2021-11-03 16.35.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1645779/243202cd-99dc-32df-1043-b9047892468e.png)

![スクリーンショット 2021-11-03 16.37.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1645779/70598f2a-ec28-c585-baba-495961307868.png)

これができればアプリは完成です！お疲れ様でした！