[React][L6] リストの項目をボタンで削除・出力する2

# 実施条件
[[React][L5] リストの項目をボタンで削除・出力する](https://qiita.com/Jochun/items/5455596a929df9d4e50e)を理解していること

# 環境
MacBook Pro (2.3 GHz 8コアIntel Core i9)
macOS 14.0（23A344）
Homebrew 4.3.8
gh 2.52.0

# ソースコードの前に
## フォルダ構成
~~~:folda_structure
├── package.json
├── public/
│   └── index.html
├── src/
│   ├── index.jsx
│   ├── Todo.jsx
│   └── styles.css
│　　
~~~

## index.html
~~~diff_html:index.html
<!DOCTYPE html>
<html lang="en">
  <head> </head>

  <body>
    <div id="root"></div>
  </body>
</html>
~~~

## index.jsx
~~~diff_jsx:index.jsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";

import { Todo } from "./Todo";

const rootElement = document.getElementById("root");
const root = createRoot(rootElement);

root.render(
  <StrictMode>
    <Todo />
  </StrictMode>
);
~~~

# ソースコード
## Todo.jsx(旧)
~~~diff_jsx:Todo.jsx
import { useState } from "react";
import "./styles.css";

export const Todo = () => {
  const [todoText, setTodoText] = useState("");
  const [incompleteTodos, setIncompleteTodos] = useState([
    "TODOです1",
    "TODOです2",
  ]);
  const [completeTodos, setCompleteTodos] = useState([
    "TODOでした1",
    "TODOでした2",
  ]);

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

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

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

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


## Todo.jsx
~~~diff_jsx:Todo.jsx
import { useState } from "react";
import "./styles.css";

export const Todo = () => {
  const [todoText, setTodoText] = useState("");
  const [incompleteTodos, setIncompleteTodos] = useState([
    "TODOです1",
    "TODOです2",
  ]);
  const [completeTodos, setCompleteTodos] = useState([
    "TODOでした1",
    "TODOでした2",
  ]);

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

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

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

+ const onClickBack = (index) => {
+   const newCompleteTodos = [...completeTodos];
+   newCompleteTodos.splice(index, 1);
+
+   const newIncompleteTodos = [...incompleteTodos, completeTodos[index]];
+
+   setCompleteTodos(newCompleteTodos);
+   setIncompleteTodos(newIncompleteTodos);
+ };

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

# 解説
## 目的
- `button`タグ(戻す)を押下した時に、配列`completeTodos`の任意の要素を削除する
- `button`タグ(戻す)を押下した時に、配列`incompleteTodos`の任意の要素を追加する

## コーディング
1. `button`タグ(戻す)を押下した際に発火する、`onClick`イベントを設定する
    ~~~diff_jsx:Todo.jsx
    -                 <button>完了</button>
    +                 <button onClick={() => onClickBack(index)}>完了</button>
    ~~~
1. `onClick`イベントで呼び出される関数を`onClickBack`として設定する
    ~~~diff_jsx:Todo.jsx
    +  const onClickBack = () => {
    + };
    ~~~

1. 配列`completeTodos`の任意の項目を指定するには、配列`completeTodos`内の何番目の配列かを見分けられれば良い
    =配列の`index`を取得する
    ~~~diff_jsx:Todo.jsx
    -         {completeTodos.map((todo) => {        
    +         {completeTodos.map((todo, index) => {
    ~~~

1. `onClickBack`に`index`を関数の引数として設定する
    ~~~diff_jsx:Todo.jsx
    +  const onClickBack = (index) => {
    + };
    ~~~

1. `onCkick`に`index`を関数の引数として設定する
    なぜ、`onClick={onClickBack(index)`と記載せずに、`onClick={() => onClickBack(index)`と記載するかは[こちら](https://ja.react.dev/learn/responding-to-events)を参照
    ~~~diff_jsx:Todo.jsx
    -                 <button>完了</button>
    +                 <button onClick={() => onClickBack(index))}>完了</button>
    ~~~

1. 最新の`未完了`の配列`completeTodos`を`newCompleteTodos`として定義する（コピーする）
    ~~~diff_jsx:Todo.jsx
      const onClickBack = () => {
    +   const newCompleteTodos = [...completeTodos];
    ~~~

1. `splice`メソッドを使って、渡ってきた第一引数`index`から第二引数`1`番目を削除する
    ~~~diff_jsx:Todo.jsx
      const onClickBack = () => {
        const newCompleteTodos = [...completeTodos];
    +   newCompleteTodos.splice(index, 1);
    ~~~

1. 最新の`完了`の配列`incompleteTodos`を`newIncompleteTodos`として定義する（コピーする）
    ~~~diff_jsx:Todo.jsx
      const onClickBack = () => {
        const newCompleteTodos = [...completeTodos];
        newCompleteTodos.splice(index, 1);
     
    +   const newIncompleteTodos = [...incompleteTodos];
    ~~~

1. 配列`incompleteTodos`に`completeTodos`内の任意の(index)の要素を追加する
    ~~~diff_jsx:Todo.jsx
      const onClickBack = () => {
        const newCompleteTodos = [...completeTodos];
        newCompleteTodos.splice(index, 1);
     
    +   const newIncompleteTodos = [...incompleteTodos, completeTodos[index]];
    ~~~

1. 配列`newIncompleteTodos`を、配列`incompleteTodos`の更新関数である`setIncompleteTodos`に引数として渡す
=配列`incompleteTodos`を更新する
    ~~~diff_jsx:Todo.jsx
      const onClickBack = () => {
        const newCompleteTodos = [...completeTodos];
        newCompleteTodos.splice(index, 1);
     
        const newIncompleteTodos = [...incompleteTodos, completeTodos[index]];
     
    +   setIncompleteTodos(newIncompleteTodos);
    ~~~

1. 配列`newIncompleteTodos`を、配列`completeTodos`の更新関数である`setCompleteTodos`に引数として渡す
=配列`completeTodos`を更新する
    ~~~diff_jsx:Todo.jsx
      const onClickBack = () => {
        const newCompleteTodos = [...completeTodos];
        newCompleteTodos.splice(index, 1);
     
        const newIncompleteTodos = [...incompleteTodos, completeTodos[index]];
     
        setIncompleteTodos(newIncompleteTodos);
    +   setCompleteTodos(newCompleteTodos);
      };
    ~~~

## 処理
1. `completeTodos`に登録されている`完了のTODO`がリストとして表示されている
1. ユーザーが`完了`ボタンを押下する
1. `onClick`イベントが発火する
1. <INS>`{completeTodos.map((todo, index) => {...}}`によって`index`が各`li`要素に渡されているので、該当の要素がわかる</INS>
1. `() => onClickBack(index)`が実行される
1. 該当の`index`が`onClickBack`に渡る
1. 最新の配列`completeTodos`を`newCompleteTodos`に渡す（コピーする）
1. `splice`メソッドを使って、`newCompleteTodos`の第一引数`index`から第二引数`1`番目を削除する
1. 最新の配列`incompleteTodos`を`newIncompleteTodos`に渡し（コピーし）、該当の`index`を配列`completeTodos`内の要素を追加する
1. `newCompleteTodos`を更新関数`setCompleteTodos`に渡す
1. `completeTodos`が更新される
1. Reactの再レンダリングがトリガーされる
1. `newIncompleteTodos`を更新関数`setIncompleteTodos`に渡す
1. `incompleteTodos`が更新される
1. Reactの再レンダリングがトリガーされる
1. `button`タグ(戻す)を押下した時に、配列`completeTodos`の任意の要素が削除・出力される

# イメージ
## Todo.jsx(旧)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2955010/0c080c6e-347e-1999-7302-976f7280bce7.png)

## Todo.jsx
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2955010/824194e5-e85e-5c47-812a-39a22ab04957.png)



# 参考リンク
- [イベントへの応答](https://ja.react.dev/learn/responding-to-events)
- [モダンJavaScriptの基礎から始める挫折しないためのReact入門
](https://www.udemy.com/course/modern_javascipt_react_beginner)