Reactでアプリを作成をしました【3】【Todoリスト①】

## 【要件】
##### 課題1. タスク追加機能
- 追加ボタン押下時にタスクを登録できる
- タスクには、「ID、コメント、状態」が含まれる
- IDは連番にする
- コメントはフォームで入力された値が表示される
- 状態には、「作業中」と表示される
- 削除ボタンが表示されている（ここでは押下時の動作はつけなくてよい)

##### 課題2. タスク削除機能
- 削除ボタン押下時にそのタスクを削除できる
- タスク削除時はIDが振り直される
- 削除後、新たにタスクを追加するとIDが連番となっている

##### 課題3. タスク状態変更機能
- タスクの状態を変更できる
- 作業中の状態でボタンをクリックすると完了へ変更される
- 完了の状態でボタンをクリックすると作業中へ変更される

##### 課題4. タスク表示/非表示切り替え機能
- タスクの状態によって表示/非表示を切り替えできる
- 選択されたラジオボタンに応じて、タスクの表示を切り替える

## 【作成にあたり学習した事】
- 要素のレンダー
- コンポーネントとprops
- stateとライフサイクル
- CRUD

※　Todoリストは基本的なシステムの主機能である『CRUD』を有する。
『CRUD』とは、『Create 作成』『Read 編集』『Update 更新』『Delete 削除』

## 課題1. タスク追加機能を作成する
- 追加ボタン押下時にタスクを登録できる
- タスクには、「ID、コメント、状態」が含まれる
- IDは連番にする
- コメントはフォームで入力された値が表示される
- 状態には、「作業中」と表示される
- 削除ボタンが表示されている（ここでは押下時の動作はつけなくてよい)

(1) Todoリストプロジェクトを新規作成

```
terminal
create-react-app <プロジェクト名>
```

(2) src　/ App.jsとindex.js以外のファイルを削除

(3)インプットエリアに入力し、追加ボタンを押したものがTodoエリアに追加されていく機能を実装
①アプリで使用する2つのstateを宣言する。

- 入力用に『todos』
- 作業中のTodo用に『comment』


## 躓いた所と解決方法
#### 課題１：躓いた所
① ID連番、コメント、作業中・削除ボタンが表示されるが、ID、コメント、状態の
欄関係なく表示される。
②　ID連番、コメント、作業中・削除ボタンが表示されるが、全てIDに表示される。
 
#### 課題１：躓いた所の解決方法

①　tableの中の要素を見直しをした。

##### 改善前のソースコード

```App.js
<table>
        <thead>
          <tr>
            <th>ID</th>
            <th>コメント</th>
            <th>状態</th>
          </tr>
        </thead>
       
        <tbody>
          <ol>
            {incompleteTodos.map((todo) => {
              return (
                <tr className="list-row" key={todo}>
                  <td>
                    <li>{todo}</li>
                  </td>
                  <td>
                    <button>作業中</button>
                    <button>削除</button>
                  </td>
                </tr>
              );
            })}
          </ol>
        </tbody>
      </table>
```

##### 改善前のソースコード

```App.js
<table>
        <thead>
          <tr>
            <th>ID</th>
            <th>コメント</th>
            <th>状態</th>
          </tr>
        </thead>
        <tbody>
          {todos.map((todo) => {
            return (
              <tr key={todo.id}>
                <td>{todo.id}</td>
                <td>{todo.comment}</td>
                <td>
                  <button>作業中</button>
                  <button>削除</button>
                </td>
              </tr>
            );
          })}
        </tbody>
      </table>
```
②　追加したtodoに振られる番号をtableタグ内のolではなく違う書き方に変えた。

```sample.js
//todoの配列を格納するstateを作成する
const [todos, setTodos] = useState([]);

//todoを追加する関数を作成する
const addTodo = () => {
  if (comment === "") return;

  const todo = {
    id: todos.length + 1,
    comment,
    status: "incomplete",
  };
  setTodos([...todos, todo]);
  setComment("");
};

//配列をループしてtodoを表示する。
//テーブルを使用する場合はtrタグ(行)をループさせてその中にtdタグ(列)を追加する。

<table>
  <thead>
    <tr>
      <th>ID</th>
      <th>コメント</th>
      <th>状態</th>
      <th>削除</th>
    </tr>
  </thead>
  <tbody>
    {todos.map((todo) => {
      return (
        <tr key={todo.id}>
          <td>{todo.id}</td>
          <td>{todo.comment}</td>
          <td>
            <button>作業中</button>
          </td>
          <td>
            <button>削除</button>
          </td>
        </tr>
      );
    })}
  </tbody>
</table>
```
#### 課題1 完成

```App.js
import { useState } from "react";

//入力用 todos
//未完了のTodo用 comment

function App() {
  const [todos, setTodos] = useState([]);
  const [comment, setComment] = useState("");

  const onChangeTodo = (e) => {
    setComment(e.target.value);
  };

  const addTodo = () => {
    if (comment === "") return;

    const todo = {
      id: todos.length + 1,
      comment,
      status: "incomplete",
    };
    setTodos([...todos, todo]);
    setComment("");

    // const newTodos = [...incompleteTodos, inputTodo];
    // setIncompleteTodos(newTodos);
    // setInputTodo("");
  };

  return (
    <div className="App-header">
      <h1>TODOリスト</h1>

      <form>
        <label>
          <input type="radio" id="all-Todo" name="Todo" defaultChecked />
          全て
        </label>
        <label>
          <input type="radio" id="incomplete-Todo" name="Todo" />
          作業中
        </label>
        <label>
          <input type="radio" id="complete-Todo" name="Todo" />
          完了
        </label>
      </form>

      <table>
        <thead>
          <tr>
            <th>ID</th>
            <th>コメント</th>
            <th>状態</th>
          </tr>
        </thead>
        <tbody>
          {todos.map((todo) => {
            return (
              <tr key={todo.id}>
                <td>{todo.id}</td>
                <td>{todo.comment}</td>
                <td>
                  <button>作業中</button>
                  <button>削除</button>
                </td>
              </tr>
            );
          })}
        </tbody>
      </table>

      <div>
        <h2>新規タスクの追加</h2>
        <input placeholder="Enter a new TODO" value={comment} onChange={onChangeTodo} />
        <button onClick={addTodo}>追加</button>
      </div>
    </div>
  );
}

export default App;
```

```index.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";

ReactDOM.render(<App />, document.querySelector("#root"));
```

## 課題2. タスク削除機能

- 削除ボタン押下時にそのタスクを削除できる
- タスク削除時はIDが振り直される
- 削除後、新たにタスクを追加するとIDが連番となっている


① 削除ボタンが押された時に押されたTodoを削除するようなクリックイベントを付与す

```sample.js
<button onClick={onClickDelete}>削除</button>
```
②onClickDeleteの関数を作成する。<br>⇨ ※追加したタスクの削除ボタンを押したら、表示が出るかどうか確認する。

```sample.js
const onClickDelete = () => {
    alert("削除");
  };
```
③削除ボタンを押した時に、動作して欲しいので新しくアロー関数を生成する。

```sample.js
{todos.map((todo, index) => {
//....
 <button onClick={() => onClickDelete(index)}>削除</button>
```

④onClickDeleteにindexを受け取る。⇨ <br>※追加したタスクの削除ボタンを押したら、『数字』表示が出るかどうか確認する。

```sample.js
const onClickDelete = (index) => {
    alert(index);
  };
```
⑤ 下記のソースコードを加える

```
const onClickDelete = (index) => {
    const newTodos = [...todos];
    newTodos.splice(index, 1);
    setTodos(newTodos);
    setTodos(newTodos.map((e, i) => ({ ...e, id: i + 1 })));
```
#### 課題２ 完成

```App.js
import { useState } from "react";

//入力用 todos
//未完了のTodo用 comment

function App() {
  const [todos, setTodos] = useState([]);
  const [comment, setComment] = useState("");

  const onChangeTodo = (e) => {
    setComment(e.target.value);
  };

  const addTodo = () => {
    if (comment === "") return;

    const todo = {
      id: todos.length + 1,
      comment,
      status: "incomplete",
    };
    setTodos([...todos, todo]);
    setComment("");
  };
  const handleDelete = (index) => {
    const newTodos = [...todos];
    newTodos.splice(index, 1);
    setTodos(newTodos);
    setTodos(newTodos.map((e, i) => ({ ...e, id: i + 1 })));
  };

  return (
    <div className="App-header">
      <h1>TODOリスト</h1>

      <form>
        <label>
          <input type="radio" id="all-Todo" name="Todo" defaultChecked />
          全て
        </label>
        <label>
          <input type="radio" id="incomplete-Todo" name="Todo" />
          作業中
        </label>
        <label>
          <input type="radio" id="complete-Todo" name="Todo" />
          完了
        </label>
      </form>

      <table>
        <thead>
          <tr>
            <th>ID</th>
            <th>コメント</th>
            <th>状態</th>
          </tr>
        </thead>
        <tbody>
          {todos.map((todo, index) => {
            return (
              <tr key={todo.id}>
                <td>{todo.id}</td>
                <td>{todo.comment}</td>
                <td>
                  <button>作業中</button>
                  <button onClick={() => handleDelete(index)}>削除</button>
                </td>
              </tr>
            );
          })}
        </tbody>
      </table>

      <div>
        <h2>新規タスクの追加</h2>
        <input placeholder="Enter a new TODO" value={comment} onChange={onChangeTodo} />
        <button onClick={addTodo}>追加</button>
      </div>
    </div>
  );
}

export default App;
```
```index.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";

ReactDOM.render(<App />, document.querySelector("#root"));
```
## 課題3. タスク状態変更機能
- タスクの状態を変更できる
- 作業中の状態でボタンをクリックすると完了へ変更される
- 完了の状態でボタンをクリックすると作業中へ変更される

①作業中/完了中の状態でボタンをクリックすると完了へ変更されるようなクリックイベントを付与す


```
<button onClick={() => toggle(todo.id)}>{todo.status === "incomplete" ? "作業中" : "完了"}</button>
```

```
const toggle = (todoId) => {
    setTodos(
      todos.map((todo, i) =>
        todoId === todo.id
          ? {
              ...todo,
              status: todo.status === "incomplete" ? "completed" : "incomplete", // completed は「完了」を表す
            }
          : todo
      )
    );
  };
```
#### 課題3 完成
```App.js
import { useState } from "react";
//入力用 todos
//未完了のTodo用 comment
function App() {
  const [todos, setTodos] = useState([]);
  const [comment, setComment] = useState("");

  const onChangeTodo = (e) => {
    setComment(e.target.value);
  };

  const addTodo = () => {
    if (comment === "") return;

    const todo = {
      id: todos.length + 1,
      comment,
      status: "incomplete",
    };
    setTodos([...todos, todo]);
    setComment("");
  };
  const handleDelete = (index) => {
    const newTodos = [...todos];
    newTodos.splice(index, 1);
    setTodos(newTodos);
    setTodos(newTodos.map((e, i) => ({ ...e, id: i + 1 })));
  };
  const toggle = (todoId) => {
    setTodos(
      todos.map((todo, i) =>
        todoId === todo.id
          ? {
              ...todo,
              status: todo.status === "incomplete" ? "completed" : "incomplete",
            }
          : todo
      )
    );
  };

  return (
    <div className="App-header">
      <h1>TODOリスト</h1>

      <form>
        <label>
          <input type="radio" id="all-Todo" name="Todo" defaultChecked />
          全て
        </label>
        <label>
          <input type="radio" id="incomplete-Todo" name="Todo" />
          作業中
        </label>
        <label>
          <input type="radio" id="complete-Todo" name="Todo" />
          完了
        </label>
      </form>

      <table>
        <thead>
          <tr>
            <th>ID</th>
            <th>コメント</th>
            <th>状態</th>
          </tr>
        </thead>
        <tbody>
          {todos.map((todo, index) => {
            return (
              <tr key={todo.id}>
                <td>{todo.id}</td>
                <td>{todo.comment}</td>
                <td>
                  <button onClick={() => toggle(todo.id)}>{todo.status === "incomplete" ? "作業中" : "完了"}</button>
                  <button onClick={() => handleDelete(index)}>削除</button>
                </td>
              </tr>
            );
          })}
        </tbody>
      </table>

      <div>
        <h2>新規タスクの追加</h2>
        <input placeholder="Enter a new TODO" value={comment} onChange={onChangeTodo} />
        <button onClick={addTodo}>追加</button>
      </div>
    </div>
  );
}

export default App;
```

## 課題4. タスク表示 / 非表示切り替え機能
- タスクの状態によって表示/非表示を切り替えできる
- 選択されたラジオボタンに応じて、タスクの表示を切り替える

①　タスクの状態によって表示/非表示を切り替えできるクリックイベントを付与す

付け加えた及び書き換えたソースコード

```App.js
//省略
const [radio, setRadio] = useState("all");
//省略
const switchTodos = () => {
    if (radio === "completed") {
      return todos.filter((todo) => todo.status === "completed");
    }
    if (radio === "incomplete") {
      return todos.filter((todo) => todo.status === "incomplete");
    }
    return todos;
  };
//省略
<form>
        <label>
          <input type="radio" value="all" onChange={(e) => setRadio(e.target.value)} checked={radio === "all"} />
          全て
        </label>
        <label>
          <input
            type="radio"
            value="incomplete"
            onChange={(e) => setRadio(e.target.value)}
            checked={radio === "incomplete"}
          />
          作業中
        </label>
        <label>
          <input
            type="radio"
            value="completed"
            onChange={(e) => setRadio(e.target.value)}
            checked={radio === "completed"}
          />
          完了
        </label>
      </form>
//省略
<input placeholder="Enter a new TODO" value={comment} onChange={(e) => setComment(e.target.value)} />
//省略
```

#### 課題4 完成

```App.js
import React, { useState } from "react";

//入力用 todos
//未完了のTodo用 comment

function App() {
  const [todos, setTodos] = useState([]);
  const [comment, setComment] = useState("");
  const [radio, setRadio] = useState("all");

  const addTodo = () => {
    if (comment === "") return;

    const todo = {
      id: todos.length + 1,
      comment,
      status: "incomplete",
    };
    setTodos([...todos, todo]);
    setComment("");
  };
  const onClickDelete = (index) => {
    const newTodos = [...todos];
    newTodos.splice(index, 1);
    setTodos(newTodos);
    setTodos(newTodos.map((e, i) => ({ ...e, id: i + 1 })));
  };
  const toggle = (todoId) => {
    setTodos(
      todos.map((todo, i) =>
        todoId === todo.id
          ? {
              ...todo,
              status: todo.status === "incomplete" ? "completed" : "incomplete", // completed は「完了」を表す
            }
          : todo
      )
    );
  };

  const switchTodos = () => {
    if (radio === "completed") {
      return todos.filter((todo) => todo.status === "completed");
    }
    if (radio === "incomplete") {
      return todos.filter((todo) => todo.status === "incomplete");
    }
    return todos;
  };

  return (
    <div className="App-header">
      <h1>TODOリスト</h1>

      <form>
        <label>
          <input type="radio" value="all" onChange={(e) => setRadio(e.target.value)} checked={radio === "all"} />
          全て
        </label>
        <label>
          <input
            type="radio"
            value="incomplete"
            onChange={(e) => setRadio(e.target.value)}
            checked={radio === "incomplete"}
          />
          作業中
        </label>
        <label>
          <input
            type="radio"
            value="completed"
            onChange={(e) => setRadio(e.target.value)}
            checked={radio === "completed"}
          />
          完了
        </label>
      </form>

      <table>
        <thead>
          <tr>
            <th>ID</th>
            <th>コメント</th>
            <th>状態</th>
          </tr>
        </thead>
        <tbody>
          {switchTodos().map((todo, index) => {
            return (
              <tr key={todo.id}>
                <td>{todo.id}</td>
                <td>{todo.comment}</td>
                <td>
                  <button onClick={() => toggle(todo.id)}>{todo.status === "incomplete" ? "作業中" : "完了"}</button>
                  <button onClick={() => onClickDelete(index)}>削除</button>
                </td>
              </tr>
            );
          })}
        </tbody>
      </table>

      <div>
        <h2>新規タスクの追加</h2>

        <input placeholder="Enter a new TODO" value={comment} onChange={(e) => setComment(e.target.value)} />
        <button onClick={addTodo}>追加</button>
      </div>
    </div>
  );
}

export default App;
```
```index.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";

ReactDOM.render(<App />, document.querySelector("#root"));
```

## 5.補足

```App.js
<form>
        <label>
          <input 
            type="radio" 
            value="all" 
            onChange={(e) => setRadio(e.target.value)} 
            checked={radio === "all"} 
            />
          全て
        </label>
        <label>
          <input
            type="radio"
            value="incomplete"
            onChange={(e) => setRadio(e.target.value)}
            checked={radio === "incomplete"}
          />
          作業中
        </label>
        <label>
          <input
            type="radio"
            value="completed"
            onChange={(e) => setRadio(e.target.value)}
            checked={radio === "completed"}
          />
          完了
        </label>
      </form>
```
#### formの中に、ラジオボタンの似たようなソースコードを３つ並べているのを、 label から /label まで、別のコンポーネントに切り出す一例。

```App.js

import React, { useState } from "react";

+const FILTER_VALUES = [
+  { value: "all", text: "全て" },
+  { value: "incomplete", text: "作業中" },
+  { value: "completed", text: "完了" }
+];
+
+const FilterButton = ({ value, text, checked, onChange }) => (
+  <label>
+    <input
+      type="radio"
+      value={value}
+      onChange={(e) => onChange(e.target.value)}
+      checked={checked}
+    />
+    {text}
+  </label>
+);

 //入力用 todos
 //未完了のTodo用 comment

 function App() {
   const [todos, setTodos] = useState([]);

//省略

<form>
 {FILTER_VALUES.map(({ value, text }) => (
+          <FilterButton
+            key={value}
+            value={value}
+            text={text}
+            checked={radio === value}
+            onChange={setRadio}
           />
+        ))}
       </form>
```

## 参考サイト
[【Reactチュートリアル】Todoリストを作ってみよう](https://hinoshin-blog.com/react-todo-list/)
[สอน ReactJS สร้าง Todo App พร้อมฟังก์ชั่น เพิ่ม ลบข้อมูลใน localStorage 👨‍💻💯](https://www.youtube.com/watch?v=FaD00obZcK0)
[ReactでTodoアプリケーションを作ってみました](https://mmtomitomimm.blogspot.com/2019/06/reacttodo.html?m=0)
[table 表要素](https://developer.mozilla.org/ja/docs/Web/HTML/Element/table)
[React Hooks と TypeScript で簡単 TODO アプリ](https://qiita.com/sprout2000/items/17c34c9050ef99280178)
[React入門チュートリアル (5) ToDoアプリを作ってみよう](https://www.hypertextcandy.com/react-tutorial-05-wrap-up-with-todo-app#%E3%83%95%E3%82%A3%E3%83%AB%E3%82%BF%E3%83%AA%E3%83%B3%E3%82%B0)
[select/optionタグ](https://code-kitchen.dev/html/select-option/)