Search code examples
laravel-livewire

Emitted function to parent livewire does not render changed data


I have full page livewire called MerchMaterials, which uses laravel layout component:

<?php

namespace App\Http\Livewire\Retailer;

use App\Interfaces\HasBreadcrumbs;
use App\Models\RcdpsMerchMaterial;
use App\View\Components\Layouts\Retailer as RetailerLayout;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Support\Collection;
use Livewire\Component;

class MerchMaterials extends Component implements HasBreadcrumbs
{
    use AuthorizesRequests;

    public $demands;
    public $materials;

    protected $listeners = [
        'add'
    ];

    public function mount($demands = new Collection())
    {
        $this->demands = $demands;
        $this->materials = RcdpsMerchMaterial::all();
        $this->authorize('viewAny', RcdpsMerchMaterial::class);
    }

    public function add(RcdpsMerchMaterial $material, $quantity)
    {
        $data = new Collection();
        $data->put('name', $material->name);
        $data->put('quantity', $quantity);
        $this->demands->put($material->id, $data);

        //dd($this->demands);
        //$this->render();
    }

    public function remove($materialId)
    {
        //unset($this->demands[$materialId]);
    }

    public function render()
    {
        return view('livewire.retailer.merch-materials')
            ->layout(RetailerLayout::class, [
                'title' => __('Materiały merch'),
                'breadcrumb' => $this->breadcrumbs()
            ]);
    }

    public function breadcrumbs(): array
    {
        return [
            __('Strefa rozwoju') => route('development-zone')
        ];
    }
}

This livewire has child livewires in loop to display some material and add them to the parent $demands property by form:

<div>
    <h5 class="py-4 text-uppercase gray"><span class="font-family-bold">{{ __('Magazyn PPSOM') }}</span>: {{ __('Zapotrzebowanie') }}</h5>
    @json($demands)
    <div class="row" wire:loading.remove>
        <div class="col-8">
            @foreach($demands as $materialId => $demand)
            @json($demand)
            @endforeach
            <table class="table">
                <thead class="font-family-bold text-uppercase bg-primary text-white">
                    <tr>
                        <th scope="col">{{ __('Materiały') }}</th>
                        <th scope="col">{{ __('Ilość') }}</th>
                        <th scope="col"></th>
                    </tr>
                </thead>
                <tbody>
            @foreach($this->demands as $materialId => $demand)
                    <tr>
                        <th class="text-uppercase" scope="row">{{ $demand['name'] }}</th>
                        <td>{{ $demand['quantity'] }}</td>
                        <td><a href="" wire:click.prevent="remove({{ $materialId }})" title="{{ __('Usuń materiał') }}"><object data="{{ asset('/images/rc-garbage-icon.svg') }}"></object></a></td>
                    </tr>
            @endforeach
                </tbody>
            </table>
        </div>
    </div>
    <x-alert />
    <div class="row">
        <div class="col-12 pt-5" wire:loading>
            <div class="ring">
                <object class="w-50" data="{{ asset('/images/rc-logo.svg') }}"></object>
                <span></span>
            </div>
        </div>
    </div>
    <div class="row g-3 py-5" wire:loading.remove>
    @forelse($materials as $material)
    <livewire:retailer.merch-material-tile class="mt-4" :material="$material" :wire:key="'material-'.$material->id"></livewire:retailer.merch-material-tile>
    @empty
        <p>{{ __('Brak dostępnych materiałów') }}</p>
    @endforelse
    </div>
</div>

Here is child livewire class:

<?php

namespace App\Http\Livewire\Retailer;

use App\Models\RcdpsMerchMaterial;
use App\View\Components\Layouts\Retailer;
use Illuminate\Support\Collection;
use Livewire\Component;

class MerchMaterialTile extends Component
{
    public $material;
    public $quantity;

    protected $rules = [
        'quantity' => 'required|numeric|integer|min:1',
    ];

    public function mount(RcdpsMerchMaterial $material)
    {
        $this->material = $material;
    }

    public function updated($propertyName)
    {
        $this->validateOnly($propertyName);
    }

    public function store()
    {
        $validatedData = $this->validate();
        //$this->reset('quantity');

        $this->emitTo('retailer.merch-materials', 'add', $this->material->id, $validatedData['quantity']);
    }

    public function render()
    {
        return view('livewire.retailer.merch-material-tile');
    }
}

Here is child livewire blade:

<div class="d-flex justify-content-center align-items-stretch col-lg-3 col-12">
    <div class="card shadow">
        <img class="card-img-top img-fluid" src="{{ $material->image }}" alt="{{ $material->name }}">
        <div class="card-body d-flex flex-column justify-content-between text-center">
            <p class="card-title font-family-bold text-uppercase">{{ $material->name }}</p>
            <p class="card-text">{{ $material->description }}</p>
        </div>
        <form class="py-5" wire:submit.prevent="store" method="post">
            @csrf
            <fieldset class="form-group">
                <div class="row justify-content-center">
                    <div class="form-floating col-md-6 col-12">
                        <input type="number" wire:model.lazy="quantity" class="form-control @error('quantity') is-invalid @enderror" id="inputQuantity" placeholder="{{ __('Ilość') }}" min="0">
                        <label for="inputQuantity">{{ __('Ilość') }}<sup class="red">*</sup></label>
                        @error('quantity')
                            <span class="invalid-feedback" role="alert">
                                <strong>{{ $message }}</strong>
                            </span>
                        @enderror
                    </div>
                </div>
            </fieldset>
            <div class="row pt-4">
                <div class="col-12 d-flex justify-content-center">
                    <x-button type="submit" class="text-uppercase text-nowrap" font="bold">{{ __('Dodaj') }}</x-button>
                </div>
            </div>
        </form>         
    </div>
</div>

Procedure is pretty simple:

  1. In child livewire I'm filling the quantity field and submit form.
  2. Form data after succesfull validation is emitted to parent to add function.
  3. Parent listen add function and add form data from child form to property $demands in parent livewire.
  4. Based on dd() after adding and debugbar everything is ok (next form submits push data to collection $demands): debugbar
  5. On parent livewire I have loop for $demands, which should be shown in view, but sadly seems like new data is not rendered for some reason.

Tried many things like refreshing livewire in add function, adding $this->render(), changing emitTo to emitUp but same result. Any clues? I have been struggling with this for two whole days now...

I would appreciate any help


Solution

  • Found problem.

    It was nothing related to above code. I had in my layout {{ $slot }} two times (second one displayed conditionally on mobile basing on bootstrap media breakpoint). Just refactored my layout to use only one {{ $slot }} solved issue.

    A light bulb went on over my head after reading this article: how-to-structure-your-layout-file-for-livewire