Search code examples
phplaravellaravel-livewirelaravel-9

Table disappears after clicking on add button on Livewire


My Livewire Component

public $productId;
public $allTariff = [];
public $rowProducts = [];

public function mount()
{
    $this->rowProducts = Products::all();

    $this->allTariff = [
            ['productId' => '', 'basicCharge' => '', 'additionalCharge' => '']
    ];
}

public function addProduct()
{

    $this->allTariff[] = ['productId' => '', 'basicCharge' => '', 'additionalCharge' => ''];
}

public function render()
{
    $rowProducts = Products::all();
    return view('livewire.admin.admin-add-tariffs-component', ['rowProducts'=>$rowProducts)->layout('layouts.admin.base');
}

My View File

<!-- Begin Page Content -->
<div class="container-fluid">
    <!-- Page Heading -->
    <div class="row">
        <div class="col-lg-8">
            <div class="card shadow mb-4">
                <div class="card-header py-3">
                    <h6 class="m-0 font-weight-bold text-primary">Add Tariff</h6>
                </div>
                <div class="card-body">
                  <form wire:submit.prevent="storeTariff">
                    @csrf
                    <div class="form-row">
                        <!-- Default input -->
                        <div class="form-group col-md-8">
                            <input type="text" class="form-control" placeholder="Enter Tariff Name" wire:model="tariffName" >
                        </div>
                    </div><hr>

                    <div class="card">
                        <div class="card-header">
                            <h6 class="text-primary">Products, Basic and Weight Charges</h6>
                        </div>
                        <div class="card-body">
                            <table class="table" id="products_table">
                                <thead>
                                    <tr>
                                        <th>Product</th>
                                        <th>Basic Charge</th>
                                        <th>Weight Charge</th>
                                        <th></th>
                                    </tr>
                                </thead>
                                <tbody>

                                    @foreach ($allTariff as $index => $value)
                                        <tr>
                                            <td>
                                                <select name="allTariff[{{$index}}][productId]"
                                                    wire:model="allTariff.{{ $index }}.productId"
                                                    class="custom-select custom-select-sm form-control form-control-sm">
                                                    @foreach ($rowProducts as $product)
                                                    <option value="{{ $product->id }}">
                                                        {{ $product->product_name }}
                                                    </option>
                                                    @endforeach
                                                </select>
                                            </td>
                                            <td>
                                                <input type="text" class="form-control form-control-user" name="allTariff[{{$index}}][basicCharge]" placeholder="Basic Charge" wire:model="allTariff.{{ $index }}.basicCharge" required>
                                            </td>
                                            <td>
                                                <input type="text" class="form-control form-control-user" name="allTariff[{{$index}}][additionalCharge]" placeholder="Weight Charge" wire:model="allTariff.{{ $index }}.additionalCharge" required>
                                            </td>
                                        </tr>
                                    @endforeach
                                </tbody>
                            </table>

                            <div class="row">
                                <div class="col-md-12">
                                    <button class="btn btn-sm btn-secondary"
                                        wire:click.prevent="addProduct">+ Add Another Product</button>
                                </div>
                            </div>
                        </div>
                    </div>

                    <hr>

                    {{-- <div class="form-row">
                        <div class="form-group col-md-3"> --}}
                            <button type="submit" class="form-control btn btn-small btn-primary">Add
                                Tariff</button>
                        {{-- </div>
                    </div> --}}
                </form>
                </div>
            </div>
        </div>
    </div>
</div>
<!-- /.container-fluid -->

Every time I click on the add tariff button this photo it give the blank page on this photo, but on console an html response is given.
I have tried a couple methods and tricks still no way out, just stuck for days

I have already include the livewire @livewireStyles and @livewireScripts, and i can't find an answer anywhere else cause i don't see any question that match my problem, and i'm kinda new to livewire


Solution

  • When using Livewire, there are a few things you need to be aware of. Due to the nature of how Livewire updates the page, there are some structural rules you need to follow.

    The first and probably most important thing here, is that every Livewire-component view should only consist of one root element. And this includes comments!

    If we count the root elements in your view, there are three - a comment, a div, and then another comment. So the first thing I did here, was to move the comments inside that div.

    <div class="container-fluid">
        <!-- Begin Page Content -->
        <!-- Page Heading -->
        <div class="row">
            <div class="col-lg-8">
                <div class="card shadow mb-4">
                    <div class="card-header py-3">
                        <h6 class="m-0 font-weight-bold text-primary">Add Tariff</h6>
                    </div>
                    <div class="card-body">
                        <form wire:submit.prevent="storeTariff">
                            @csrf
                            <div class="form-row">
                                <!-- Default input -->
                                <div class="form-group col-md-8">
                                    <input type="text" class="form-control" placeholder="Enter Tariff Name"
                                        wire:model="tariffName">
                                </div>
                            </div>
                            <hr>
    
                            <div class="card">
                                <div class="card-header">
                                    <h6 class="text-primary">Products, Basic and Weight Charges</h6>
                                </div>
                                <div class="card-body">
                                    <table class="table" id="products_table">
                                        <thead>
                                            <tr>
                                                <th>Product</th>
                                                <th>Basic Charge</th>
                                                <th>Weight Charge</th>
                                                <th></th>
                                            </tr>
                                        </thead>
                                        <tbody>
    
                                            @foreach ($allTariff as $index => $value)
                                                <tr>
                                                    <td>
                                                        <select name="allTariff[{{ $index }}][productId]"
                                                            wire:model="allTariff.{{ $index }}.productId"
                                                            class="custom-select custom-select-sm form-control form-control-sm">
                                                            @foreach ($rowProducts as $product)
                                                                <option value="{{ $product->id }}">
                                                                    {{ $product->product_name }}
                                                                </option>
                                                            @endforeach
                                                        </select>
                                                    </td>
                                                    <td>
                                                        <input type="text" class="form-control form-control-user"
                                                            name="allTariff[{{ $index }}][basicCharge]"
                                                            placeholder="Basic Charge"
                                                            wire:model="allTariff.{{ $index }}.basicCharge" required>
                                                    </td>
                                                    <td>
                                                        <input type="text" class="form-control form-control-user"
                                                            name="allTariff[{{ $index }}][additionalCharge]"
                                                            placeholder="Weight Charge"
                                                            wire:model="allTariff.{{ $index }}.additionalCharge"
                                                            required>
                                                    </td>
                                                </tr>
                                            @endforeach
                                        </tbody>
                                    </table>
    
                                    <div class="row">
                                        <div class="col-md-12">
                                            <button class="btn btn-sm btn-secondary" wire:click.prevent="addProduct">+ Add Another Product</button>
                                        </div>
                                    </div>
                                </div>
                            </div>
    
                            <hr>
    
                            {{-- <div class="form-row">
                            <div class="form-group col-md-3"> --}}
                            <button type="submit" class="form-control btn btn-small btn-primary">Add
                                Tariff</button>
                            {{-- </div>
                        </div> --}}
                        </form>
                    </div>
                </div>
            </div>
        </div>
        <!-- /.container-fluid -->
    </div>
    

    Then another thing I would recommend that you look into is using wire:key on the tr element inside your table-loop, and on the option element inside your inner loop. The value to wire:key should always be unique to that particular row, so using $loop->index is not generally advised. You can generate a dummy-ID for each record that you add to your array, which sole purpose is to track the individual row. Here's how, see the wire:key I added in the template,

    <div class="container-fluid">
        <!-- Begin Page Content -->
        <!-- Page Heading -->
        <div class="row">
            <div class="col-lg-8">
                <div class="card shadow mb-4">
                    <div class="card-header py-3">
                        <h6 class="m-0 font-weight-bold text-primary">Add Tariff</h6>
                    </div>
                    <div class="card-body">
                        <form wire:submit.prevent="storeTariff">
                            @csrf
                            <div class="form-row">
                                <!-- Default input -->
                                <div class="form-group col-md-8">
                                    <input type="text" class="form-control" placeholder="Enter Tariff Name"
                                        wire:model="tariffName">
                                </div>
                            </div>
                            <hr>
    
                            <div class="card">
                                <div class="card-header">
                                    <h6 class="text-primary">Products, Basic and Weight Charges</h6>
                                </div>
                                <div class="card-body">
                                    <table class="table" id="products_table">
                                        <thead>
                                            <tr>
                                                <th>Product</th>
                                                <th>Basic Charge</th>
                                                <th>Weight Charge</th>
                                                <th></th>
                                            </tr>
                                        </thead>
                                        <tbody>
    
                                            @foreach ($allTariff as $index => $value)
                                                <tr wire:key="tariff-{{ $value->wireKey }}">
                                                    <td>
                                                        <select name="allTariff[{{ $index }}][productId]"
                                                            wire:model="allTariff.{{ $index }}.productId"
                                                            class="custom-select custom-select-sm form-control form-control-sm">
                                                            @foreach ($rowProducts as $product)
                                                                <option value="{{ $product->id }}" wire:key="product-{{ $product->id }}">
                                                                    {{ $product->product_name }}
                                                                </option>
                                                            @endforeach
                                                        </select>
                                                    </td>
                                                    <td>
                                                        <input type="text" class="form-control form-control-user"
                                                            name="allTariff[{{ $index }}][basicCharge]"
                                                            placeholder="Basic Charge"
                                                            wire:model="allTariff.{{ $index }}.basicCharge" required>
                                                    </td>
                                                    <td>
                                                        <input type="text" class="form-control form-control-user"
                                                            name="allTariff[{{ $index }}][additionalCharge]"
                                                            placeholder="Weight Charge"
                                                            wire:model="allTariff.{{ $index }}.additionalCharge"
                                                            required>
                                                    </td>
                                                </tr>
                                            @endforeach
                                        </tbody>
                                    </table>
    
                                    <div class="row">
                                        <div class="col-md-12">
                                            <button class="btn btn-sm btn-secondary" wire:click.prevent="addProduct">+ Add Another Product</button>
                                        </div>
                                    </div>
                                </div>
                            </div>
    
                            <hr>
    
                            {{-- <div class="form-row">
                            <div class="form-group col-md-3"> --}}
                            <button type="submit" class="form-control btn btn-small btn-primary">Add
                                Tariff</button>
                            {{-- </div>
                        </div> --}}
                        </form>
                    </div>
                </div>
            </div>
        </div>
        <!-- /.container-fluid -->
    </div>
    

    And then in your component, just generate a unique string

    class AdminAddTariffsComponent 
    {
        public $productId;
        public $allTariff = [];
        public $rowProducts = [];
    
        public function mount()
        {
            $this->rowProducts = Products::all();
    
            $this->addProduct();
        }
    
        public function addProduct()
        {
            $this->allTariff[] = [
                'productId' => '', 
                'basicCharge' => '', 
                'additionalCharge' => '',
                'wireKey' => \Str::uuid(),
            ];
        }
    
        public function render()
        {
            return view('livewire.admin.admin-add-tariffs-component')
                ->layout('layouts.admin.base');
        }
    }