React / TODOアプリの作成 / 実装方法

#記事の内容
これまで学んだ知識を活かしつつ、JavaScriptの関数等を使用することでTODOアプリを無事作成することができました。

今回学んだことをアウトプットの意味も込めて実装内容を解説しておりますので、興味がある方は是非ともご覧くださいませ。

【**追伸**】
作成したTODOアプリをもとにコンポーネント化してみたので興味のある方は、下記URLから御覧くださいませ。

https://qiita.com/taddy_korokoro/items/06a82f97c3bd10265c6f

##完成形
**最後に完成形のソースコードを載せています。**

[![Image from Gyazo](https://i.gyazo.com/f05cdcbcf7edc47eceec1424c2423bf2.gif)](https://gyazo.com/f05cdcbcf7edc47eceec1424c2423bf2)

##前提

- `CodeSandbox`を使用

https://codesandbox.io/

##実装手順

| 実装順 | ページ内リンク |
|:----:|:-------------|
| 1 | [実装する前の準備](#実装する前の準備) |
| 2 | [タスクの追加機能](#ステートの管理) |
| 3 | [タスクの削除機能](#タスクの削除機能) |
| 4 | [タスクの完了機能](#タスクの完了機能) |
| 5 | [タスクの戻す機能](#タスクの戻す機能) |
| 6 | [完成形のソースコード](#完成形のソースコード) |


#実装する前の準備
**ひな形の作成 ＋ スタイルの付与を行い、静的なTODOアプリを作成しております。**

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

import { App } from "./App";

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

```react:src/App.jsx
import React from "react";
import "./styles.css";

export const App = () => {
  return (
    <>
      <div className="input-area">
        <input placeholder="TODOを入力" />
        <button>追加</button>
      </div>
      <div className="incomplete-area">
        <p className="title">未完了のTODO</p>
        <ul>
          <div className="list-row">
            <li>タスク1</li>
            <button>完了</button>
            <button>削除</button>
          </div>
          <div className="list-row">
            <li>タスク2</li>
            <button>完了</button>
            <button>削除</button>
          </div>
        </ul>
      </div>
      <div className="complete-area">
        <p className="title">完了のTODO</p>
        <ul>
          <div className="list-row">
            <li>タスク3</li>
            <button>戻る</button>
          </div>
        </ul>
      </div>
    </>
  );
};
```

* `jsx`の場合、クラスを付与する際は `className="クラス名"`とする必要がある。

```css:src/styles.css
body {
  font-family: sans-serif;
}

input {
  border-radius: 16px;
  border: none;
  padding: 6px 16px;
  outline: none;
}

button {
  border-radius: 16px;
  border: none;
  padding: 4px 16px;
}

button:hover {
  background-color: #ff7fff;
  color: #fff;
  cursor: pointer;
}

li {
  margin-right: 8px;
}

.input-area {
  background-color: #c1ffff;
  width: 400px;
  height: 30px;
  border-radius: 8px;
  padding: 8px;
  margin: 8px;
}

.incomplete-area {
  background-color: #c6ffe2;
  width: 400px;
  min-height: 200px;
  padding: 8px;
  margin: 8px;
  border-radius: 8px;
}

.complete-area {
  background-color: #ffffe0;
  width: 400px;
  min-height: 200px;
  padding: 8px;
  margin: 8px;
  border-radius: 8px;
}

.title {
  text-align: center;
  margin-top: 0;
  font-weight: bold;
  color: #666;
}

.list-row {
  display: flex;
  align-items: center;
  padding-bottom: 4px;
}
```

###<--- ここから機能の実装開始 --->

#ステートの管理
**状態が変化するリストを`useState`で定義**して動的に表示させます。

```react:src/App.jsx
import React, { useState } from "react"; // useStateの読み込み
import "./styles.css";

export const App = () => {
  // 未完了TODOを定義
  const [incompleteTodos, setIncompleteTodos] = useState([
    "タスク1",
    "タスク2"
  ]);
  // 完了TODOを定義
  const [completeTodos, setCompleteTodos] = useState(["タスク3"])
  return (
    <>
      <div className="input-area">
        <input placeholder="TODOを入力" />
        <button>追加</button>
      </div>
      <div className="incomplete-area">
        <p className="title">未完了のTODO</p>
        <ul>
          {incompleteTodos.map((todo) => {
            return (
              <div key={todo} className="list-row">
                <li>{todo}</li>
                <button>完了</button>
                <button>削除</button>
              </div>
            );
          })}
        </ul>
      </div>
      <div className="complete-area">
        <p className="title">完了のTODO</p>
        <ul>
          {completeTodos.map((todo) => {
            return (
              <div key={todo} className="list-row">
                <li>{todo}</li>
                <button>戻る</button>
              </div>
            );
          })}
        </ul>
      </div>
    </>
  );
};
```

* **未完了のTODO**と**完了のTODO**を格納する変数を定義
    * TODOは複数作成できるようにするため、配列`[]`で定義
        * `incompleteTodos`には未完了のTODOが格納（`setIncompleteTodos`は後ほど解説します）
            * 画面に反映されるか確認するため、配列に仮値を格納しています。
    * `map`関数でループ処理
        * JavaScriptを記述するため、**ブラケット**`{}`でコードを囲う。
        * `map`の引数を設定 -> `map((todo ← 引数) => {`
            * 引数には、先ほど定義した配列の仮値が格納されている（引数名は任意）
        * TODOの表示
            * `<li>{todo}</li>`とすることで配列に格納されている要素を順番に表示
        * `key`の設定
            * ループ内で返却している親タグに`key={引数名}`を記述
            * `React`は変更前と変更後（ここではTODOの追加や削除等の変更時）の**差分のみ**反映してレンダリングされるため、`key`で要素を識別するための目印として記述する必要があります。

#タスクの追加機能

**TODOを入力して追加ボタンをクリック後、未完了のTODOリストにタスクが追加されるまで**

```react:src/App.jsx
export const App = () => {
  const [todoText, setTodoText] = useState("");

  const [incompleteTodos, setIncompleteTodos] = useState([
    "タスク1",
    "タスク2"
  ]);
  const [completeTodos, setCompleteTodos] = useState(["タスク3"]);

  const onChangeTodoText = (event) => setTodoText(event.target.value);

  const onClickAdd = () => {
    if (todoText === "") return;
    const newTodos = [...incompleteTodos, todoText];
    setIncompleteTodos(newTodos);
    setTodoText("");
  };
  return (
    <>
      <div className="input-area">
        <input
          placeholder="TODOを入力"
          value={todoText}
          onChange={onChangeTodoText}
        />
        <button onClick={onClickAdd}>追加</button>
      </div>
```

**の実装内容を解説します。**

```react
const [todoText, setTodoText] = useState("");
```

* TODO入力欄の値を取得するため、入力部分を`state`として定義
    * 入力欄は最初は空なので`useState('')`として**空文字**を設定

```react
<input placeholder="TODOを入力" value={todoText} onChange={onChangeTodoText} />
```

* `input`の`value`属性に`{todoText}`を設定することで、入力された値が表示されるようにしている。
    * 現時点では、空文字が設定した状態のため、入力しても値は反映されないので、、
* `onChange`イベントを使用して入力された値を取得し、入力欄に反映させる処理を関数に定義している。

```react
const onChangeTodoText = (event) => setTodoText(event.target.value);
```
* 関数の引数(`event`)には、入力された情報が格納されており、`event.target.value`とすることで入力された値を取得できる。
    * 取得した値を`setTodoText`の引数に渡すことで、`todoText`の`state`に反映させている。

```react
<button onClick={onClickAdd}>追加</button>
```

* `onClick`イベントを使用し、追加ボタン押下後の処理を定義した関数を設定

```react
  const onClickAdd = () => {
    if (todoText === "") return;
    const newTodos = [...incompleteTodos, todoText];
    setIncompleteTodos(newTodos);
    setTodoText("");
  };
```

* **if文**で入力欄が空の場合、処理を実行しないよう制御している。
* `newTodos`変数には、今現在表示されているタスクと新たに追加したタスクを結合した新たな配列が格納されている。
    * **スプレッド構文`(...)`**を使用して今現在表示されているタスクを格納
    * `todoText`には、新たに追加したタスクが格納されている。
* `setIncompleteTodos`の引数に渡すことで更新された内容が反映されて、未完了のTODOリストに表示される。
* 最後に`setTodoText("");`とすることで、追加後に入力欄を**リセット**している。

#タスクの削除機能
**削除ボタン押下後、該当タスクを削除**

```react:src/App.jsx
export const App = () => {
  .
  .
  .
  const onClickDelete = (index) => {
    const newTodos = [...incompleteTodos];
    newTodos.splice(index, 1);
    setIncompleteTodos(newTodos);
  };
  return (
    <>
      .
      .
      .
        <p className="title">未完了のTODO</p>
        <ul>
          {incompleteTodos.map((todo, index) => {
            return (
              <div key={todo} className="list-row">
                <li>{todo}</li>
                <button>完了</button>
                <button onClick={() => onClickDelete(index)}>削除</button>
              </div>
```

**実装内容を解説します。**

```react
<button onClick={() => onClickDelete(index)}>削除</button>
```

* 削除ボタンに`onClick`イベントを設定
    * `map`関数の第2引数に設定した`index`には、タスクの**インデックス番号**が格納されており、その値をもとに削除したタスクの判定するため、`onClickDelete`関数の引数に渡している。
        * 関数に引数を渡す場合、アロー関数 `() => ` で新しく関数を生成させることで関数の実行を抑えている。
            * 記述しない場合、削除ボタンを押す前に関数が実行されてしまう。

```react
const onClickDelete = (index) => {
  const newTodos = [...incompleteTodos];
  newTodos.splice(index, 1);
  setIncompleteTodos(newTodos);
};
```

* `newTodos`(現在の未完了TODO)から`splice`メソッドを使用して削除したタスクを配列から取り除き、新たな配列を生成している。
    * `splice`の引数・・・（要素のインデックス番号, 取り除く要素数）
* 最後に`setIncompleteTodos`に更新後の配列を渡している。

#タスクの完了機能
**完了ボタン押下後、未完了のTODOリストからタスクを削除し、完了のTODOリストに表示させる。**

```react:src/App.jsx
export const App = () => {
  .
  .
  .
  const onClickComplete = (index) => {
    const newIncompleteTodos = [...incompleteTodos];
    newIncompleteTodos.splice(index, 1);
    setIncompleteTodos(newIncompleteTodos);

    const newCompleteTodos = [...completeTodos, incompleteTodos[index]];
    setCompleteTodos(newCompleteTodos);
  };
  return (
    <>
      .
      .
      .
        <p className="title">未完了のTODO</p>
        <ul>
          {incompleteTodos.map((todo, index) => {
            return (
              <div key={todo} className="list-row">
                <li>{todo}</li>
                <button onClick={() => onClickComplete(index)}>完了</button>
                <button onClick={() => onClickDelete(index)}>削除</button>
              </div>
```

**実装内容を解説します。**

```react
<button onClick={() => onClickComplete(index)}>完了</button>
```

* `onClick`イベントは、先ほどの削除ボタンと同様

```react
const onClickComplete = (index) => {
  const newIncompleteTodos = [...incompleteTodos];
  newIncompleteTodos.splice(index, 1);
  setIncompleteTodos(newIncompleteTodos);

  const newCompleteTodos = [...completeTodos, incompleteTodos[index]];
  setCompleteTodos(newCompleteTodos);
};
```

* 未完了のTODOリストからタスクを削除する実装は、変数名以外は先ほどと同様
* `[...completeTodos, incompleteTodos[index]];`
    * `newCompleteTodos`変数には、**完了TODOリストに表示されているタスク**と**完了したタスク**を結合した新たな配列が格納されている。
        * 配列「`incompleteTodos`」のあとに、ブラケット記法 `[]`で完了したタスクのインデックス番号「`index`」を設定することで完了したタスクを取得できる。
* 最後に`setCompleteTodos`に更新後の配列を渡している。

#タスクの戻す機能
**戻すボタン押下後、完了のTODOリストからタスクを削除し、未完了のTODOリストに表示させる。**

```react:src/App.jsx
export const App = () => {
  .
  .
  .
  const onClickBack = (index) => {
    const newCompleteTodos = [...completeTodos];
    newCompleteTodos.splice(index, 1);
    setCompleteTodos(newCompleteTodos);

    const newIncompleteTodos = [...incompleteTodos, completeTodos[index]];
    setIncompleteTodos(newIncompleteTodos);
  };
  return (
    <>
      .
      .
      .
      <div className="complete-area">
        <p className="title">完了のTODO</p>
        <ul>
          {completeTodos.map((todo, index) => {
            return (
              <div key={todo} className="list-row">
                <li>{todo}</li>
                <button onClick={() => onClickBack(index)}>戻る</button>
              </div>
```

**実装内容を解説します。**

```react
{completeTodos.map((todo, index) => {
  return (
    <div key={todo} className="list-row">
      <li>{todo}</li>
      <button onClick={() => onClickBack(index)}>戻る</button>
```

* `onClick`イベントを設定し、戻したタスクを取得するために`map`関数の引数に`index`を追加しています。
* 完了のTODOリストからタスクを削除及び、未完了のTODOリストにタスクを表示する実装は、変数名以外は先ほどと同様です。

#完成形のソースコード
**最後に配列の初期値を削除し、空の配列`[]`を設定したらTODOアプリの完成です。**

```react:src/App.jsx
import React, { useState } from "react";
import "./styles.css";

export const App = () => {
  const [todoText, setTodoText] = useState("");

  const [incompleteTodos, setIncompleteTodos] = useState([]);
  const [completeTodos, setCompleteTodos] = useState([]);

  const onChangeTodoText = (event) => setTodoText(event.target.value);

  const onClickAdd = () => {
    if (todoText === "") return;
    const newTodos = [...incompleteTodos, todoText];
    setIncompleteTodos(newTodos);
    setTodoText("");
  };

  const onClickDelete = (index) => {
    const newTodos = [...incompleteTodos];
    newTodos.splice(index, 1);
    setIncompleteTodos(newTodos);
  };

  const onClickComplete = (index) => {
    const newIncompleteTodos = [...incompleteTodos];
    newIncompleteTodos.splice(index, 1);
    setIncompleteTodos(newIncompleteTodos);

    const newCompleteTodos = [...completeTodos, incompleteTodos[index]];
    setCompleteTodos(newCompleteTodos);
  };

  const onClickBack = (index) => {
    const newCompleteTodos = [...completeTodos];
    newCompleteTodos.splice(index, 1);
    setCompleteTodos(newCompleteTodos);

    const newIncompleteTodos = [...incompleteTodos, completeTodos[index]];
    setIncompleteTodos(newIncompleteTodos);
  };
  return (
    <>
      <div className="input-area">
        <input
          placeholder="TODOを入力"
          value={todoText}
          onChange={onChangeTodoText}
        />
        <button onClick={onClickAdd}>追加</button>
      </div>
      <div className="incomplete-area">
        <p className="title">未完了のTODO</p>
        <ul>
          {incompleteTodos.map((todo, index) => {
            return (
              <div key={todo} className="list-row">
                <li>{todo}</li>
                <button onClick={() => onClickComplete(index)}>完了</button>
                <button onClick={() => onClickDelete(index)}>削除</button>
              </div>
            );
          })}
        </ul>
      </div>
      <div className="complete-area">
        <p className="title">完了のTODO</p>
        <ul>
          {completeTodos.map((todo, index) => {
            return (
              <div key={todo} className="list-row">
                <li>{todo}</li>
                <button onClick={() => onClickBack(index)}>戻る</button>
              </div>
            );
          })}
        </ul>
      </div>
    </>
  );
};
```

##参考教材

https://www.udemy.com/course/modern_javascipt_react_beginner