[React][L4] リストの項目をボタンで削除する

# 実施条件
[[React][L3] テキストボックスの内容を出力する](https://qiita.com/Jochun/items/d66d0959ff6fca45f180)を理解していること

# 環境
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("");
    }
  };

  return (
    <>
      <div className="input-area">
        <input placeholder="TODOを入力"></input>
        <input placeholder="TODOを入力" value={todoText} onChange={onChangeTodoText}></input>
        <button>追加</button>
        <button onClick={onClickAdd}>追加</button>
      </div>
      <div className="incomplete-area">
        <p className="title">未完了のTODO</p>
        <ul>
          {incompleteTodos.map((todo) => {
            return (
              <li key={todo}>
                <div className="list-row">
                  <p>{todo}</p>
                  <button>完了</button>
                  <button>削除</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);
+ };

  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) => {        
+         {incompleteTodos.map((todo, index) => {
            return (
              <li key={todo}>
                <div className="list-row">
                  <p>{todo}</p>
                  <button>完了</button>
-                 <button>削除</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>
    </>
  );
};
~~~

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

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

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

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

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

1. 最新の配列`incompleteTodos`を`newTodos`として定義する
    ~~~diff_jsx:Todo.jsx
       const onClickDelete = (index) => {
    +   const newTodos = [...incompleteTodos];
      };
    ~~~

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

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

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

# イメージ
## Todo.jsx(旧)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2955010/ebdd2ea0-8020-7779-4e0e-506b9db8c01a.png)

## Todo.jsx
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2955010/093344e3-1a32-a13c-dd43-94e4adc773ee.png)



# 参考リンク
- [Array.prototype.map()](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/map#%E5%BC%95%E6%95%B0)
- [イベントへの応答](https://ja.react.dev/learn/responding-to-events)
- [Array.prototype.splice()](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/splice)
- [モダンJavaScriptの基礎から始める挫折しないためのReact入門
](https://www.udemy.com/course/modern_javascipt_react_beginner)