livewireで入力フォームの数を動的に制御

# はじめに
livewireで入力フォームの数を動的に増やしたり消したりする機能を実装する機会があったので、メモがてら。
<a id="#環境"></a>
# 環境
Laravel8.x, livewire, bootstrap4
参照：[【Laravel】Laravel-AdminLTEとlivewireを布教したい](https://qiita.com/sgrs38/items/89a6a59b896669dfd725)
laravel8系とbootstrap4があれば何でも良いです。

<!-- 各チャプター -->
<a id="#完成品"></a>
# 完成品

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/282328/1df08501-0e54-4fe1-a521-dd14a55b2f4d.png)
追加ボタンクリックで「商品名、価格、削除ボタン」がセットのフォームを追加
削除ボタンクリックで指定のフォームの削除
登録ボタンで何かしらの処理実行


<a id="#コード"></a>
# コード

コマンドでlivewireファイルを作成
<pre>php artisan make:livewire Sample0404</pre>

```php:app/Http/Livewire/Sample0404.php
<?php

namespace App\Http\Livewire;

use Livewire\Component;

class Sample0404 extends Component
{
    public $products = [];

    protected $rules = [
        'products.*.name' => 'required',
        'products.*.price' => 'required',
    ];

    protected $validationAttributes = [
        'products.*.name' => '商品名',
        'products.*.price' => '価格',
    ];

    public function mount()
    {
        $this->products = [
            ['name' => 'みかん','price'=>2000],
            ['name' => 'りんご','price'=>1000]
        ];
    }

    public function render()
    {
        return view('livewire.sample0404')
            ->extends('adminlte::page')
            ->section('content');
    }

    public function add()
    {
        $this->products[] = ['name' => null,'price' =>null];
    }

    public function delete($key)
    {
        unset($this->products[$key]);
    }

    public function create()
    {
        $this->validate();
        dd('ok');
    }
}
```

```html:resources/views/livewire/sample0404.blade.php
<div class="pt-2">
    <div class="col-5">
        <button type="button" class="btn btn-info col-2 mb-3" wire:click.prevent="add()">追加</button>
        @foreach($products as $key => $product)
            <div class="card card-body">
                <div class="text-right">
                    <button type="button" class="btn btn-danger col-2" wire:click.prevent="delete({{$key}})">削除</button>
                </div>
                <div class="row">
                    <div class="form-group col-md-6">
                        <label for="products.{{ $key }}.name">商品名</label>
                        <input type="text" name="products.{{ $key }}.name" class="form-control"
                               id="products.{{ $key }}.name" wire:model.defer="products.{{ $key }}.name">

                        @error("products.$key.name")
                        <span class="invalid-feedback d-block" role="alert">
                                    <strong>{{ $errors->first("products.$key.name") }}</strong>
                                </span>
                        @enderror
                    </div>

                    <div class="form-group col-md-6">
                        <label for="products.{{ $key }}.price">価格</label>
                        <input type="text" name="products.{{ $key }}.price" class="form-control"
                               id="products.{{ $key }}.price" wire:model.defer="products.{{ $key }}.price">

                        @error("products.$key.price")
                        <span class="invalid-feedback d-block" role="alert">
                                    <strong>{{ $errors->first("products.$key.price") }}</strong>
                                </span>
                        @enderror
                    </div>
                </div>
            </div>
        @endforeach
        <button type="button" class="btn btn-success col-2" wire:click.prevent="create">登録</button>
    </div>
</div>

```
<a id="#ざっくり説明"></a>
# ざっくり説明

**1.mountメソッドに既存データを入れる**
```php:
    public function mount()
    {
        $this->products = [
            ['name' => 'みかん','price'=>2000],
            ['name' => 'りんご','price'=>1000]
        ];
    }
```

mountメソッドはページアクセス時のみに一度だけ動く処理
DB連携する場合は既に登録されているデータを配列化して入れましょう。あくまで一例です。
```php:
    public function mount()
    {
        $this->products = Hoge::select(['name','price'])->get(['name','price'])->toArray();
    }
```

**2.addメソッドでproduts配列に連想配列を追加**
```html:
<button type="button" class="btn btn-info col-2 mb-3" wire:click.prevent="add()">追加</button>
```
```php:
    public function add()
    {
        $this->products[] = ['name' => null,'price' =>null];
    }
```
配列を追加することでblade側のforeachで回される要素が増えるので画面に１つフォームが追加されます。

**3.deleteメソッドでフォームを削除**

```javascript:
<button type="button" class="btn btn-danger col-2" wire:click.prevent="delete({{$key}})">削除</button>
```
```php:
    public function delete($key)
    {
        unset($this->products[$key]);
    }
```
deleteメソッドに配列のキーを渡して配列から削除します。画面上に反映されるとフォームが消えます

<a id="#まとめ"></a>
# まとめ

こんな感じにphpの記述だけで動的にフォームを追加したり削除したりできます。
あくまで簡易的な一例なので、ちゃんとDBと連携する場合には要件に合わせてロジックを考えましょう。