View is not refreshing when the collection is updated

I use nested components like so:

Blade:

<ul>
    @foreach($notes as $note)
    <li>
        <livewire:note :note="$note" :key="$note->id" />
    </li>
    @endforeach
</ul>

Component:

class NotesList extends Component
{
    public Collection $notes;
    public int $someId;

    public $listeners = [
        'notes:update' => 'loadNotes',
        'notes:remove' => 'loadNotes',
        'notes:create' => 'loadNotes'
    ];

    public function mount(int $someId): void
    {
        $this->someId= $someId;
        $this->loadNotes();
    }

    public function loadNotes(): void
    {
        $this->notes = Note::where('some_id', $this->someId)->latest('date')->get();
    }

    public function render(): View
    {
        return view('livewire.notes-list');
    }
}

The child component emits a create, update and remove event. I want the list to refresh, when such event is emitted. In my example above this works perfectly for the create and remove event. However for the update event it is not working. The note is not refreshing in the list. When I dd($this->notes) in loadNotes, $this->notes actually has all the changes, but the view is not re-rendered.

Why is that so?

2 Likes

This happened to me before. Try this:

class NotesList extends Component
{
    public $listeners = ['refreshNotes' => '$refresh'];

    public function getNotesProperty()
    {
        return Notes::query()->get();
    }

    public function render()
    {
        return view('livewire.notes-list', [
            'notes' => $this->notes,
        ]);
    }
}

and then you may emitUp('refreshNotes') from the child component.

You can read this out.

1 Like

Hey, @artworkad

After updating your collection use

$yourModel->refresh()
1 Like

Hi @skywalker , can it render the view ? How do you implemented in Livewire ?
Btw, I’ve just found out there’s a refresh method on model. :smiley: I will look into it. Thanks!

1 Like

There is no refresh method when the model is a collection. And is $refresh documented somewhere? I just tried the exact same setup like in your suggestion and the list is not updated. When I add to the list or remove from it, $refresh updates the list. However editing an item from the list, does not trigger a refresh. Is there some magic in refresh, that determines if a refresh should happen or not?

Where is the save method when editing, the List component or Note component ? Because i expect <livewire:note /> is child component. is that right?

What i have experienced. I created the List as parent component, save method is in the parent. and make getListeners() in child component.

in child component:

    public function getListeners(): array
    {
        return [
            'note.updated.' . $this->note->id => '$refresh',
        ];
    }

so after save/update, the parent will call $this->emit('note.updated.' . $this-note->id)

parent component:

public function save()
{
    $this->note->save();
    $this->emit('note.updated.' . $this-note->id);
}

Thank you for the example. In my case it is a little bit different. So yes, there is a list component and each list item is a sub component. However the editing happens in a modal. The modal emits a note.updated event which the list components listenes too. The listener method is triggered, but nothing in this method will refresh the list component.

When adding a note, I send the same event and the same listeners refreshes the list component. This is weired. My idea is that livewire uses some magic to determine that a model/collection has changed.

Just found the fix. Add time() to the key of the subcomponent:

:key="time().$note->id"

It is a dom diffing issue described here: https://laravel-livewire.com/docs/2.x/troubleshooting

Sources:

2 Likes

By design, changes in the DOM do not automatically propagate down. There is a series of YouTube videos with Matt Stauffer and Caleb where they convert a Vue component to a Livewire component.

One of the videos discusses events and nesting where Caleb talks about communication between components.

I had a similar issue and resolved it using dynamic listeners which Caleb mentions in the above video and @gwillyoo has suggested.

protected $listeners = [
  // some listeners
];

public function getListeners()
{
  return array_merge($this->listeners, [
    "note-{$this->note->id}" => '$refresh',
  ];
}

When your modal emits an event to the NoteList component and that event is processed, emit another event to just your specific Note component. The difference between this and the use of :key="time().$note->id" is that only the targeted Note component is re-rendered.

2 Likes

Thanks for showing the yt link @Veleous. I followed them but never knew it. :smile_cat: