React.jsで複数の入力フォームをもつ要素郡を一つのユニットとして動的に追加削除ができるように実現する｡

##動機づけ
**普段は､Vue.jsを使って業務のUIやUX(というと大げさですが)など利用者が入力しやすいフォームなどを
構築しています､
そういった作業の中でよくあるフォーム仕様として､
1.ある飲食店の情報を複数登録したい
2.だが､入力項目は複数あって
3.好きな数だけ追加し
4.かつ､もし入力済みの店舗情報を常に書き直し修正が可能で､自由に任意の位置の店舗情報を削除したい
といった入力フォーム内容を仰せつかることがあります｡それをReact.jsで初心者ながら作ってみました｡**


*とりあえず､トレーニング用としてCDNでためしてみました｡*

##コード全体

```react.js

<html>

  <script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script>
  <script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
  <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
  <!-- CSS only -->
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-giJF6kkoqNQ00vy+HMDP7azOuL0xtbfIcaT9wjKHr8RbDVddVHyTfAAsrekwKmP1" crossorigin="anonymous">
  <!-- JavaScript Bundle with Popper -->
  <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/js/bootstrap.bundle.min.js" integrity="sha384-ygbV9kiqUc6oa4msXn9868pTtWMgiQaeYH7/t7LECLbyPA2x65Kgf80OJFdroafW" crossorigin="anonymous"></script>
  <style>
    .shop-info-block {
      width: 80%;
      margin: 2% auto 5% auto;
      border: 1px solid #AAA;
      text-align: center;
      font-size: 15pt;
    }
    .shop-info-block input{
      width: 80%;
      font-size: 15pt;
    }

    .modal-to-check-shop-info {
      border: 2px dashed #554433;
    }
    #add-shop-block {
      width: 80%;
      margin: 2% auto 5% auto;
    }
    #add-shop-block button{
      margin: 2% auto 5% auto;
      border: 1px solid #AAA;
      text-align: center;
      font-size: 15pt;
    }
  </style>
<body>

  <div id="root"></div>


  <script type="text/babel">


    const ShopComponent = (props) => {

      const [movieList, setMovieList] = React.useState([{
        shop_name: "超男",
        shop_name_sort: "ちょうおとこ",
        address: "DB漫画",
        phone_number: "久楽賢人",
        email: "1980/12/08"
      }]);


      const updateMovie = (index, event) => {
        console.log("index =>" + index)
        console.log("event.target.name => " + event.target.name);
        console.log("event.target.value => " + event.target.value);
        const newMovieList = [...movieList];
        newMovieList[index][event.target.name] = event.target.value;
        setMovieList(newMovieList);
        props.getMovieList(newMovieList);
      }

      const deleteMovie = (index) => {
        console.log("index => " + index + " を削除する");

        const newMovieList = [...movieList];
        newMovieList.splice(index, 1);
        console.log(movieList);
        console.log(newMovieList);
        setMovieList(newMovieList);
        props.getMovieList(newMovieList);
      }

      const addMovie = () => {
        const newMovieList = [...movieList, {
          shop_name: "超男",
          shop_name_sort: "ちょうおとこ",
          address: "DB漫画",
          phone_number: "久楽賢人",
          email: "1980/12/08"
        }];
        setMovieList(newMovieList);
      }

      return (
        <React.Fragment>
          {movieList.map((data, index) => {
            return (
              <React.Fragment key={index + data}>
                <div className="shop-info-block">
                  <h3>映画情報{index}番目</h3>
                  <p>映画名:<input type="text" name="shop_name" value={data.shop_name} onInput={(e) => updateMovie(index, e)}></input></p>
                  <p>映画目(ひらがな):<input type="text" name="shop_name_sort" value={data.shop_name_sort} onInput={(e) => updateMovie(index, e)}></input></p>
                  <p>配給会社:<input type="text" name="address" value={data.address} onInput={(e) => updateMovie(index, e)}></input></p>
                  <p>主演俳優:<input type="text" name="phone_number" value={data.phone_number} onInput={(e) => updateMovie(index, e)}></input></p>
                  <p>公開時期:<input type="text" name="email" value={data.email} onInput={(e) => updateMovie(index, e)}></input></p>
                  <button onClick={() => deleteMovie(index)}>この映画を削除する</button>
                </div>
                <div className="shop-info-block">
                <p><input type="text" readOnly="readonly" value={data.shop_name} /></p>
                <p><input type="text" readOnly="readonly" value={data.shop_name_sort} /></p>
                <p><input type="text" readOnly="readonly" value={data.address} /></p>
                <p><input type="text" readOnly="readonly" value={data.phone_number} /></p>
                <p><input type="text" readOnly="readonly" value={data.email} /></p>
                </div>
              </React.Fragment>
            )
          })}
          <button onClick={() => addMovie()}>この映画情報を追加する</button>
        </React.Fragment>
      )
    };


    // 店舗の入力内容を確認する
    const CheckShopInfo = (props) => {

      // console.log("====> 確認用コンポーネント");
      // console.log(props.movieDataList);
      // console.log("<==== 確認用コンポーネント");
      return (
        <React.Fragment>
          <div id="modal-to-check-shop-info">
            {props.movieDataListProps.map((data, index) => {
              return(
                <div key={index}>
                  <p>{data.shop_name}</p>
                  <p>{data.shop_name_sort}</p>
                  <p>{data.address}</p>
                  <p>{data.phone_number}</p>
                  <p>{data.email}</p>
                  <hr/>
                </div>
              );
            })}
          </div>
        </React.Fragment>
      )
    }

    const App = (props) => {

      const [movieList , setMovieList] = React.useState([]);

      const getMovieList = (movieList) => {
        setMovieList(movieList)
      }
      return (
        <React.Fragment>
          <div className="row">
            <div className="col">
              <p>一つのユニット(要素)中に複数の入力項目があるフォームを動的に追加する</p>
              <p>かつ､任意のindexのユニットを自由に削除し､また､最後尾に自由にユニットを追加する</p>

              <ShopComponent getMovieList={getMovieList}/>


            </div>
            <div className="col">
              <div id="add-shop-block">
                {movieList.map((data, index) => {
                  return (
                    <div key={index}>
                      <h3>現在の入力内容はこちら </h3>
                      <p >{data.shop_name}</p>
                      <p >{data.shop_name_sort}</p>
                      <p >{data.address}</p>
                      <p >{data.phone_number}</p>
                      <p >{data.email}</p>
                      <hr/>
                    </div>
                  )
                })}
              </div>
            </div>
          </div>
        </React.Fragment>
      );
    }



    ReactDOM.render(
      <App myProperty="独自プロパティ" />,
      document.getElementById("root")
    )
  </script>
</body>
</html>


```

##実際の入力フォームをSubComponentとして定義する

```react.js

    const ShopComponent = (props) => {

      const [movieList, setMovieList] = React.useState([{
        shop_name: "超男",
        shop_name_sort: "ちょうおとこ",
        address: "DB漫画",
        phone_number: "久楽賢人",
        email: "1980/12/08"
      }]);


      const updateMovie = (index, event) => {
        console.log("index =>" + index)
        console.log("event.target.name => " + event.target.name);
        console.log("event.target.value => " + event.target.value);
        const newMovieList = [...movieList];
        newMovieList[index][event.target.name] = event.target.value;
        setMovieList(newMovieList);
        props.getMovieList(newMovieList);
      }

      const deleteMovie = (index) => {
        console.log("index => " + index + " を削除する");

        const newMovieList = [...movieList];
        newMovieList.splice(index, 1);
        console.log(movieList);
        console.log(newMovieList);
        setMovieList(newMovieList);
        props.getMovieList(newMovieList);
      }

      const addMovie = () => {
        const newMovieList = [...movieList, {
          shop_name: "超男",
          shop_name_sort: "ちょうおとこ",
          address: "DB漫画",
          phone_number: "久楽賢人",
          email: "1980/12/08"
        }];
        setMovieList(newMovieList);
      }

      return (
        <React.Fragment>
          {movieList.map((data, index) => {
            return (
              <React.Fragment key={index + data}>
                <div className="shop-info-block">
                  <h3>映画情報{index}番目</h3>
                  <p>映画名:<input type="text" name="shop_name" value={data.shop_name} onInput={(e) => updateMovie(index, e)}></input></p>
                  <p>映画目(ひらがな):<input type="text" name="shop_name_sort" value={data.shop_name_sort} onInput={(e) => updateMovie(index, e)}></input></p>
                  <p>配給会社:<input type="text" name="address" value={data.address} onInput={(e) => updateMovie(index, e)}></input></p>
                  <p>主演俳優:<input type="text" name="phone_number" value={data.phone_number} onInput={(e) => updateMovie(index, e)}></input></p>
                  <p>公開時期:<input type="text" name="email" value={data.email} onInput={(e) => updateMovie(index, e)}></input></p>
                  <button onClick={() => deleteMovie(index)}>この映画を削除する</button>
                </div>
                <div className="shop-info-block">
                <p><input type="text" readOnly="readonly" value={data.shop_name} /></p>
                <p><input type="text" readOnly="readonly" value={data.shop_name_sort} /></p>
                <p><input type="text" readOnly="readonly" value={data.address} /></p>
                <p><input type="text" readOnly="readonly" value={data.phone_number} /></p>
                <p><input type="text" readOnly="readonly" value={data.email} /></p>
                </div>
              </React.Fragment>
            )
          })}
          <button onClick={() => addMovie()}>この映画情報を追加する</button>
        </React.Fragment>
      )
    };

```

Vue.jsからReact.jsになんの予備知識もないまま入り込んだ日まず､面食らったのは､Vue.jsのように
デフォルトで双方向がサポートされてない?というところでした｡

Vue.jsだと

```vue.js
<input type="text" v-model="movie_name" />
```

上記のような感じで､簡単に双方向なやり取りができるのに､React.jsでは hookなる機能をつかって
自分で､双方向なやり取りが可能なように手配してあげなければいけないようでした｡

ちょこちょこっと調べたところ

```react.js

const [shopName, setShopName] = React.useState("");
const [shopEmail, setShopEmail] = React.useState("");
const [shopAddress, setShopAddress] = React.useState("");

```
上記のような処理を必要な項目分追加しないといけないのか...と考えたら気が滅入ってやっぱりVue.jsのままでいいやと思ったんですが,やはり自身の技術力向上という点でなんとか知恵をふりしぼってみました｡

初心者ゆえ､このやり方でよいかどうかは迷っているのですが､

```react.js

<p>映画名:<input type="text" name="shop_name" value={data.shop_name} onInput={(e) => updateMovie(index, e)}></input></p>
<p>映画目(ひらがな):<input type="text" name="shop_name_sort" value={data.shop_name_sort} onInput={(e) => updateMovie(index, e)}></input></p>
<p>配給会社:<input type="text" name="address" value={data.address} onInput={(e) => updateMovie(index, e)}></input></p>
<p>主演俳優:<input type="text" name="phone_number" value={data.phone_number} onInput={(e) => updateMovie(index, e)}></input></p>
<p>公開時期:<input type="text" name="email" value={data.email} onInput={(e) => updateMovie(index, e)}></input></p>

```

上記のような必要な入力フォームにname属性で取り回しやすい属性値を付与して

```react.js

const updateMovie = (index, event) => {
  console.log("index =>" + index)
  console.log("event.target.name => " + event.target.name);
  console.log("event.target.value => " + event.target.value);
  const newMovieList = [...movieList];
  newMovieList[index][event.target.name] = event.target.value;
  setMovieList(newMovieList);
  props.getMovieList(newMovieList);
}

```
上記のように､onInputイベント発生時に取得できるeventオブジェクトから操作中のname属性を取得して､
状態(state)をアップデートする処理を書いてみました｡
少なくともこれで､入力項目数分のhookのuseStateを記述せずにすむので､これでOKじゃん!と思ってはいますが､
どうなのでしょうか?
知見のある方､もしこれがバッドノウハウであれば､イディオム的なやり方を指摘いただけるとありがたいです｡


あと､つまり気味になっていた箇所が､
任意の箇所の枠を削除して､不要なものを消したいという処理です｡

```react.js
// ただしく動作する
<button onClick={() => deleteMovie(index)}>この映画を削除する</button>
```

上記の箇所で最初は

```react.js
// 望まない動作をする
<button onClick={deleteMovie(index)}>この映画を削除する</button>
```
上記のように記述してうまく動作していませんでしたが､イベントハンドラに無名関数で関数を返してやるようにしたらうまく動作しました｡


あとは､実際に入力した内容をフォーム以外の場所の要素に確認として表示させたかたかったのですが､
それが以下の箇所です｡

```react.js
  <div className="shop-info-block">
  <p><input type="text" readOnly="readonly" value={data.shop_name} /></p>
  <p><input type="text" readOnly="readonly" value={data.shop_name_sort} /></p>
  <p><input type="text" readOnly="readonly" value={data.address} /></p>
  <p><input type="text" readOnly="readonly" value={data.phone_number} /></p>
  <p><input type="text" readOnly="readonly" value={data.email} /></p>
  </div>
</React.Fragment>
```
しかしながら､どうも入力項目が正しく､別の箇所のHTML箇所に反映されず望んだリアクティブな動作をしてくれなかったのですが､

```react.js
const updateMovie = (index, event) => {
  console.log("index =>" + index)
  console.log("event.target.name => " + event.target.name);
  console.log("event.target.value => " + event.target.value);
  const newMovieList = [...movieList];
  newMovieList[index][event.target.name] = event.target.value;
  setMovieList(newMovieList);
  props.getMovieList(newMovieList);
}
```
上記の箇所で

```react.js
  const newMovieList = [...movieList];
```
と配列のクローンを返してその帰ってきたクローンに対して更新処理をしてあげると
正しくリアクティブになりました｡

このあたりの挙動がまだ正確に把握できていません｡

が､とりいそぎよくあるフォームの要素を動的に追加､任意の箇所を削除といった仕様がなんとかカバーできそうなので初心者的にはここまで一旦良しとしました｡

ただ､この複数の入力フォームをもつ要素を配列としてもってやってよいのかどうかは､わかりません｡
つまり､

```react
[{
// 複数の入力項目要素
},
{
// 複数の入力項目要素
},
{
// 複数の入力項目要素
}]
```

という構造でもたせてよいのか あるいは複数の入力フォームそのものをコンポーネント化して､
追加を行う場合は､コンポーネントを動的に追加してやるのがよいのかもしれません｡
この点についても､識者の方ご指摘いただけるとありがたいです｡