The challenge: We have an unusually long list of items, displayed as cards on a single page. We are using a modal to update those items. On save, we only want that specific card to re-render. We don't want nor need the whole list page (parent component to re-render).
There are just a few livewire components in play here:
- The Index component, which hosts the list of $items using a @foreach, and displays an edit button next to each card
- The Card component, which is passed the $item and displays the $item information in the card, as well as timestamp of last render
- The Edit component, a modal which when triggered is passed an $item to update, and then emits a change event when $item is saved.
The App
The Index component code
class Index extends Component
{
public $tasks;
public function render()
{
$this->tasks = Task::all();
return view('livewire.index');
}
}
The Index component blade
<div class="py-10 w-full flex
justify-center min-h-screen items-center
bg-black text-white
text-sm"
<div class="w-full">
@foreach($tasks as $task)
<div wire:key="task-{{$loop->index}}">
<livewire:task.card :task="$task"/>
<button
id="complete-{{$loop->index}}"
wire:click="$emitTo('task.edit', 'show', {{$task->id}})">
Edit
</button>
</div>
@endforeach
</div>
<livewire:task.edit />
</div>
The Edit component code
class Edit extends Component
{
protected $listeners = ['show'];
public $task;
public $showing = false;
protected $rules = [
'task.status' => 'required',
'task.name' => 'required|string|max:255'
];
public function show(Task $task){
$this->task = $task;
$this->showing = true;
}
public function render(){
return view('livewire.task.edit');
}
public function save(){
$this->validate();
$this->task->save();
$this->showing = false;
//tell card to refresh
$this->emit('update-task-' . $this->task->id);
}
}
The Edit component blade
<div class="{{$showing? '' : 'hidden'}}"
wire:click="$set('showing',false)">
@if($task)
<form wire:click.stop>
<div>
Task
<input type="text" wire:model="task.name">
</div>
<div>Status
<select wire:model="task.status">
<option value="Delayed">Delay</option>
<option value="Completed">Complete</option>
<option value="Pending">Not Started</option>
<option value="Canceled">Cancel</option>
</select>
</div>
<div class="w-full text-right">
<button
wire:click.prevent="$set('showing', false)"
>
Cancel
</button>
<button wire:click.prevent="save">
Save
</button>
</div>
</form>
@endif
</div>
Dynamic Livewire Listeners
There's nothing unusual in the above code, the modal hide/show is pretty standard, and we have the Edit button emit an event that populates the Edit component with a specific task ands unhides the modal. For brevity I removed the tailwind classes, so this will look a little weird, but it works.
Instead of using AlpineJS to show hide I just went with standard blade/livewire. The part I want to draw attention to in the above code snippets is the last line of the save function in the Edit Component:
$this->emit('update-task-' . $this->task->id);
Here we are emitting an event that is very specific to just this $task. Now let's look at the code behind of the Card component:
class Card extends Component
{
public $listeners = []; // must be public!
public $task;
public function mount($task){
// add listener specific to this task only.
$this->listeners =["update-task-" . $task->id =>"updateTask"];
}
public function updateTask(){
// calling this triggers render,
// no code needed here
}
public function render(){
return view('livewire.task.card');
}
}
Here I've added a mount() function that adds a $task->id specific listener, and points to an empty function. Now, when a $task is edited and saved, ONLY this Card instance will be listening for that specific event, and only this card will refresh, the rest of the Index page will stay unaltered.
Source code for this project can be found (using Laravel 9, PHP 8.1, Tailwind 2) here
Top comments (1)
Thank you for sharing. I was stuck with creating dynamic listeners in Livewire and this post helped me generate some ideas. In my case, I'm trying to update Livewire page content based on events generated by Laravel Echo.