[React][L7] コンポーネン化する(Propsの利用)

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

# 環境
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/
│   ├── components/ 
│   │   ├── InputTodo.jsx
│   │   ├── IncompleteTodos.jsx
│   │   └── CompleteTodos.jsx
│   ├── 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);
  };

  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, index) => {
            return (
              <li>
                <div className="list-row">
                  <p>{todo}</p>
                  <button onClick={() => onClickBack(index)}>戻す</button>
                </div>
              </li>
            );
          })}
        </ul>
      </div>
    </>
  );
};
~~~


## Todo.jsx
~~~diff_jsx:Todo.jsx
import { useState } from "react";
+import { CompleteTodos } from "./components/CompleteTodos";
+import { IncompleteTodos } from "./components/IncompleteTodos";
+import { InputTodo } from "./components/InputTodo";
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="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>
+     <IncompleteTodos
+       todos={incompleteTodos}
+       onClickComplete={onClickComplete}
+       onClickDelete={onClickDelete}
+     /> 
      <div className="complete-area">
        <p className="title">完了のTODO</p>
        <ul>
          {completeTodos.map((todo, index) => {
            return (
              <li>
                <div className="list-row">
                  <p>{todo}</p>
                  <button onClick={() => onClickBack(index)}>戻す</button>
                </div>
              </li>
            );
          })}
        </ul>
      </div>
    </>
  );
};
~~~

# 解説
## 目的
- `return`以下の各`div`タグ（`input-area` `incomplete-area` `complete-area`）をコンポーネント化する

## コーディング
### `InputTodo.jsx`のコンポーネント化  
1. `components`ディレクトリと各コンポーネントを書くフォルダを作成する
    ~~~diff:folda_structure
     ├── package.json
     ├── public/
     │   └── index.html
     ├── src/
    +│   ├── components/ 
    +│   │   └── InputTodo.jsx
     │   ├── index.jsx
     │   ├── Todo.jsx
     │   └── styles.css
    ~~~

1. コンポーネントの雛形を用意する
    ~~~diff_jsx:InputTodo.jsx
    export const InputTodo = () => {
      return (
      );
    };
    ~~~

1. コンポーネント化したい機能を、元のファイルから切り取る
    ~~~diff_jsx:Todo.jsx
      return (
        <>
    -     <div className="input-area">
    -       <input
    -         placeholder="TODOを入力"
    -         value={todoText}
    -         onChange={onChangeTodoText}
    -       ></input>
    -       <button onClick={onClickAdd}>追加</button>
    -     </div>
    ~~~
    
    ~~~diff_jsx:InputTodo.jsx
    export const InputTodo = () => {
      return (
    +   <div className="input-area">
    +     <input
    +       placeholder="TODOを入力"
    +       value={todoText}
    +       onChange={onChangeTodoText}
    +     ></input>
    +     <button onClick={onClickAdd}>追加</button>
    +   </div>
      );
    };
    ~~~

1. `todoText`(配列) `onChangeTodoText`(関数) `onClickAdd`(関数)がないためエラーとなる
1. 上記の配列や関数を`InputTodo.jsx`が`Props`を受け取れる状態にする
    ~~~diff_jsx:InputTodo.jsx
    -export const InputTodo = () => {
    +export const InputTodo = (props) => {
      return (
        <div className="input-area">
          <input
            placeholder="TODOを入力"
            value={todoText}
            onChange={onChangeTodoText}
          ></input>
          <button onClick={onClickAdd}>追加</button>
        </div>
      );
    };
    ~~~

1. `Todo.jsx`が`Props`を送れる状態にする
    ~~~diff_jsx:Todo.jsx
    import { useState } from "react";
    +import { InputTodo } from "./components/InputTodo";
    import "./styles.css";
    
    //省略//
    
      return (
        <>
    +     <InputTodo
    +     />
    ~~~

1. `Todo.jsx`が送る`Props`を設定する
    ~~~diff_jsx:Todo.jsx
    import { useState } from "react";
    import { InputTodo } from "./components/InputTodo";
    import "./styles.css";
    
    //省略//
    
      return (
        <>
          <InputTodo
    +       todoText={todoText}
    +       onChange={onChangeTodoText}
    +       onClick={onClickAdd}
          />
    ~~~

1. `InputTodo.jsx`が受け取る`Props`を設定する
    ~~~diff_jsx:InputTodo.jsx
    export const InputTodo = (props) => {
    + const { todoText, onChange, onClick } = prorps;
      return (
        <div className="input-area">
          <input
            placeholder="TODOを入力"
            value={todoText}
            onChange={onChangeTodoText}
          ></input>
          <button onClick={onClickAdd}>追加</button>
        </div>
      );
    };
    ~~~

1. 受け取る`Props`に合わせて変数名を修正する
    ~~~diff_jsx:InputTodo.jsx
    export const InputTodo = (props) => {
    + const { todoText, onChange, onClick } = prorps;
      return (
        <div className="input-area">
          <input
            placeholder="TODOを入力"
            value={todoText}
    -       onChange={onChangeTodoText}
    +       onChange={onChange}        
          ></input>
    -     <button onClick={onClickAdd}>追加</button>
    +     <button onClick={onClick}>追加</button>
        </div>
      );
    };
    ~~~

### `IncompleteTodos`のコンポーネント化 
1. `components`ディレクトリと各コンポーネントを書くフォルダを作成する
    ~~~diff:folda_structure
     ├── package.json
     ├── public/
     │   └── index.html
     ├── src/
     │   ├── components/ 
     │   │   ├── InputTodo.jsx
    +│   │   └── IncompleteTodos.jsx
     │   ├── index.jsx
     │   ├── Todo.jsx
     │   └── styles.css
    ~~~

1. コンポーネントの雛形を用意する
    ~~~diff_jsx:IncompleteTodos.jsx
    export const IncompleteTodos = () => {
      return (
      );
    };
    ~~~
1. コンポーネント化したい機能を、元のファイルから切り取る
    ~~~diff_jsx:Todo.jsx
      return (
      
      //省略//
        
    -     <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>
    ~~~
    
    ~~~diff_jsx:IncompleteTodos.jsx
    export const IncompleteTodos = () => {
      return (
    +   <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>
      );
    };
    ~~~

1. `incompleteTodos`(配列) `onClickComplete`(関数) `onClickDelete`(関数)がないためエラーとなる
1. 上記の配列や関数を`IncompleteTodos.jsx`が`Props`を受け取れる状態にする
    ~~~diff_jsx:IncompleteTodos.jsx
    -export const IncompleteTodos = () => {
    +export const IncompleteTodos = (props) => {
      return (
        <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>
      );
    };
    ~~~

1. `Todo.jsx`が`Props`を送れる状態にする
    ~~~diff_jsx:Todo.jsx
    import { useState } from "react";
    import { InputTodo } from "./components/InputTodo";
    +import { IncompleteTodos } from "./components/InCompleteTodos";
    import "./styles.css";
      
    //省略//
        
      return (
      
      //省略//
        
    +     <IncompleteTodos 
    +     />
    ~~~

1. `Todo.jsx`が送る`Props`を設定する
    ~~~diff_jsx:Todo.jsx
    import { useState } from "react";
    import { InputTodo } from "./components/InputTodo";
    +import { IncompleteTodos } from "./components/InCompleteTodos";
    import "./styles.css";
      
    //省略//
        
      return (
      
      //省略//
        
          <IncompleteTodos
    +       todos={incompleteTodos}
    +       onClickComplete={onClickComplete}
    +       onClickDelete={onClickDelete}
          />
    ~~~

1. `IncompleteTodos.jsx`が受け取る`Props`を設定する
    ~~~diff_jsx:IncompleteTodos.jsx
    export const IncompleteTodos = (props) => {
    + const { todos, onClickComplete, onClickDelete } = props;
      return (
        <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>
      );
    };
    ~~~

1. 受け取る`Props`に合わせて変数名を修正する
    ~~~diff_jsx:IncompleteTodos.jsx
    export const IncompleteTodos = (props) => {
    + const { todos, onClickComplete, onClickDelete } = props;
      return (
        <div className="incomplete-area">
          <p className="title">未完了のTODO</p>
          <ul>
    -       {incompleteTodos.map((todo, index) => {
    +       {todos.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>
      );
    };
    ~~~


### `CompleteTodos`のコンポーネント化 
1. `components`ディレクトリと各コンポーネントを書くフォルダを作成する
    ~~~diff:folda_structure
     ├── package.json
     ├── public/
     │   └── index.html
     ├── src/
     │   ├── components/ 
     │   │   ├── InputTodo.jsx
     │   │   ├── IncompleteTodos.jsx
    +│   │   └── CompleteTodos.jsx
     │   ├── index.jsx
     │   ├── Todo.jsx
     │   └── styles.css
    ~~~
1. コンポーネントの雛形を用意する
    ~~~diff_jsx:CompleteTodos.jsx
    export const CompleteTodos = () => {
      return (
      );
    };
    ~~~
1. コンポーネント化したい機能を、元のファイルから切り取る
~~~diff_jsx:Todo.jsx
  return (
  
  //省略//
  
-     <div className="complete-area">
-       <p className="title">完了のTODO</p>
-       <ul>
-         {completeTodos.map((todo, index) => {
-           return (
-             <li>
-               <div className="list-row">
-                 <p>{todo}</p>
-                 <button onClick={() => onClickBack(index)}>戻す</button>
-               </div>
-             </li>
-           );
-         })}
-       </ul>
-     </div>
~~~

~~~diff_jsx:CompleteTodos.jsx
export const CompleteTodos = () => {
  return (
+   <div className="complete-area">
+     <p className="title">完了のTODO</p>
+     <ul>
+       {completeTodos.map((todo, index) => {
+         return (
+           <li>
+             <div className="list-row">
+               <p>{todo}</p>
+               <button onClick={() => onClickBack(index)}>戻す</button>
+             </div>
+           </li>
+         );
+       })}
+     </ul>
+   </div>
  );
};
~~~

1. `completeTodos`(配列) `onClickBack`(関数)がないためエラーとなる
1. 上記の配列や関数を`CompleteTodos.jsx`が`Props`を受け取れる状態にする
~~~diff_jsx:CompleteTodos.jsx
-export const CompleteTodos = () => {
+export const CompleteTodos = (props) => {
  return (
    <div className="complete-area">
      <p className="title">完了のTODO</p>
      <ul>
        {completeTodos.map((todo, index) => {
          return (
            <li>
              <div className="list-row">
                <p>{todo}</p>
                <button onClick={() => onClickBack(index)}>戻す</button>
              </div>
            </li>
          );
        })}
      </ul>
    </div>
  );
};
~~~

1. `Todo.jsx`が`Props`を送れる状態にする
~~~diff_jsx:Todo.jsx
import { useState } from "react";
import { InputTodo } from "./components/InputTodo";
+import { CompleteTodos } from "./components/CompleteTodos";
import { IncompleteTodos } from "./components2/IncompleteTodos";

//省略//

  return (
  
  //省略//

+     <CompleteTodos 
+     />  
~~~

1. `Todo.jsx`が送る`Props`を設定する
~~~diff_jsx:Todo.jsx
import { useState } from "react";
import { InputTodo } from "./components/InputTodo";
+import { CompleteTodos } from "./components/CompleteTodos";
import { IncompleteTodos } from "./components2/IncompleteTodos";

//省略//

  return (
  
  //省略//

      <CompleteTodos
+       todos={completeTodos} 
+       onClickBack={onClickBack} />
      />
~~~

1. `CompleteTodos.jsx`が受け取る`Props`を設定する
~~~diff_jsx:CompleteTodos.jsx
export const CompleteTodos = (props) => {
+ const { todos, onClickBack } = props;
  return (
    <div className="complete-area">
      <p className="title">完了のTODO</p>
      <ul>
        {completeTodos.map((todo, index) => {
          return (
            <li>
              <div className="list-row">
                <p>{todo}</p>
                <button onClick={() => onClickBack(index)}>戻す</button>
              </div>
            </li>
          );
        })}
      </ul>
    </div>
  );
};
~~~

1. 受け取る`Props`に合わせて変数名を修正する
~~~diff_jsx:CompleteTodos.jsx
export const CompleteTodos = (props) => {
  const { todos, onClickBack } = props;
  return (
    <div className="complete-area">
      <p className="title">完了のTODO</p>
      <ul>
-       {completeTodos.map((todo, index) => {
+       {todos.map((todo, index) => {
          return (
            <li>
              <div className="list-row">
                <p>{todo}</p>
                <button onClick={() => onClickBack(index)}>戻す</button>
              </div>
            </li>
          );
        })}
      </ul>
    </div>
  );
};
~~~

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