bionary's avatar

Array Property Losing Records after Update

I am attempting to get familiar with Livewire's states (if that is even the correct term) regarding an array or collection of Records.

When the array is updated it appears to lose the previous records. Strangely the array's length seems to be correct but the output on the blade template side is not showing what I would expect.

I'm sure I'm simply missing something trivial and some help would be appreciated.

<?php

namespace App\Http\Livewire;

use App\Models\Artwork;
use Livewire\Component;

class Counter extends Component
{
	public $count=0;
	public $artworks=[];

	public function increment(){
		$this->count++;
		array_push($this->artworks, Artwork::inRandomOrder()->first());
	}
    public function render()
    {
        return view('livewire.counter');
    }
}
<div>
    <p>count:{{$count}}</p>
    <button wire:click="increment">+</button>
    <hr>
    @foreach($artworks as $artwork)
        <li>{{$artwork->title ?? 'obj is null'}}</li>
    @endforeach
</div>

With the first update, via the button click I can see a $artwork->title properly output. BUT, additional clicks seems to nullify any previous records in the array. Output becomes:

obj is null
obj is null
obj is null
Autem Dicta Sunt Ut Tempore Exercitationem
0 likes
7 replies
webrobert's avatar

@bionary,

I am attempting to get familiar with Livewire's states (if that is even the correct term) regarding an array or collection of Records.

There are several course here on livewire I suggest watching them. It will really help grasping how it works, and why its rad - IMHO.

bionary's avatar

@webrobert Yes I did the course by Andre already and spend no less than 3 hours reading the docs and trying this out. That's why I'm asking here. Now back to getting help. Do you have any solutions?

webrobert's avatar

@bionary I just stepped out for lunch. Ill try and explain it concisely when I get home. Unless anyone else want to take a pass.

1 like
webrobert's avatar
Level 51

@bionary,

So livewire is two way. Your data is getting passed back and forth... php to javasctipt then back again. So you second round, the model is pass back from the view and now it's become an array.

** Edit: the above isn't entirely accurate. Better said, from the docs...

How the he*k does this work?

  • Livewire renders the initial component output with the page (like a Blade include). This way, it's SEO friendly.
  • When an interaction occurs, Livewire makes an AJAX request to the server with the updated data.
  • The server re-renders the component and responds with the new HTML.
  • Livewire then intelligently mutates DOM according to the things that changed.

So $artwork->title no longer works when it gets return to the view. Run this code and have a look... as it dumps out the model then the next pass its an array.

<div>
    <p>count:{{$count}}</p>
    <button wire:click="increment">{{ $count }}+</button>
    <hr>
    @forelse($artworks as $artwork)
        <li wire:key="$artwork['id']">
			{{ $artwork['name'] ?? 'obj is null'}}
        	@dump($artwork)
        </li>
    @empty
        <li>nothing yet</li>
    @endforelse
</div>
bionary's avatar

@webrobert Thank you for the explanation and sample code. That actually makes a lot of sense. I was under the impression that livewire was "rehydrating" each of the models.

Strange, I see nothing but examples using property/arrow syntax all over the examples and in the docs, but in this case I need to use a regular old array syntax. That's fine, now I know!

Out of curiosity I removed the wire:key="$artwork['id']" and it still works. I wonder just how necessary that is? If it is now just a plain numeric array that is I wonder what the point is. ( I have seen that here and there in examples )

webrobert's avatar

@bionar,

Just habit for the wire key. Do make sure to read the troubleshooting section of the docs. When items are removed or added livewire needs to track the elements so its just to be safe. I fooled around with your code sample.. Not sure if this is how I would do it ultimately but it was fun to play around with...

    public Collection $artworks;
    public bool $noMoreArt;

    public function mount() {$this->artworks = collect();}

    public function increment()
    {
        $newArt = Artwork::query()
                          ->take(1)
                          ->when( $this->artworks, fn($q) =>
                                $q->whereNotIn('id', $this->artworks->pluck('id') ))
                          ->inRandomOrder()
                          ->first();

        if(! $newArt) return $this->noMoreArt = true;

        $this->artworks = $this->artworks->push($newArt);
    }
<div class="p-8">

    @if(!$noMoreArt)
    <button wire:click="increment" class="bg-blue-600 rounded-md p-3 py-2 disabled:opacity-50 w-32 text-sm">
        {{ $artworks->count() ? $artworks->count() . '+' : 'add art' }}
    </button>
    @endif

    @if($noMoreArt)
    <div class="bg-orange-300 p-3 py-2 rounded-md text-sm">Dude, that's it! No more artwork available.</div>
    @endif

    <ul class="flex flex-col gap-2 mt-4">
    @forelse($artworks as $artwork)
        <li class="bg-gray-100 px-2 py-1 rounded-md">{{$artwork['name'] ?? 'obj is null'}}</li>
    @empty
        <li class="bg-gray-200 px-2 py-1 rounded-md">no art yet.</li>
    @endforelse
    </ul>

</div>
1 like
bionary's avatar

@webrobert Thanks again I fully appreciate the above and beyond example. Your query with the when() clause is elegant; more than likely better than anything I would have conjured up! In my real code I had the exact same thing, casting as a collection and assigning the empty collection in mount(). Every time I do something like that I always wish to myself... "hmmm hopefully the next release of php allows property assignment of non-standard class types!" I've been wishing for a long time I might add!

Thanks again friend!

1 like

Please or to participate in this conversation.