Need a place for your Laravel app in the cloud? Fly it with Fly.io, it’ll be up and running in minutes!
Imagine having a table with ten rows. Imagine even more, having a “Bulk Action” button above this table. When the user selects all ten rows from the table and clicks on the button, all ten rows are sent to the server for bulk processing.
Each row is processed one after the other, until finally, all rows complete and the server responds back to the client. This setup means that even though a portion of the group might’ve already completed its processing, this portion will not be updated as “processed” in the UI just yet—not until all other remaining parts of the group complete as well.
Well, what if, we want to progressively update the UI as each row completes? Do we create a new connection to poll for and reflect updates? Or do we create a web socket connection to listen for this change continuously?
Not quite! We don’t have to do any of the two above. Cause here comes Livewire’s wire:stream
directive! Our ones-top directive for progressively listening and reflecting changes from the server.
Let’s get to it!
What We’ll Do
To showcase Livewire’s wire:stream
directive, today, we’ll make use of a table displaying rows of “properties for sale”. Each row will have a corresponding checkbox a user can use to select different properties, and a button to send selected rows to the server for bulk processing.
Finally, we’ll make use of Livewire’s wire:stream
directive to update our UI as each row completes processing, showing as each row goes through “WIP” and finally “Processed”—all this with only one request to the server.
Set Up
Start with creating a Livewire component with php artisan livewire:make my-table
. This component’s view will contain a table, a bunch of rows, and a checkbox per row:
<div>
<table>
<thead>
<tr>
<th></th>
<th>Name</th>
<th>Processed</th>
</tr>
</thead>
<tbody>
<!-- List real estate properties -->
@foreach( $properties as $prop )
<tr>
<!-- Yes Checkbox for each row! -->
<td><input type="checkbox"></td>
<td>{{$prop['name']}}</td>
<td> TBD </td>
</tr>
@endforeach
</tbody>
{{ $properties->links() }}
It’ll also include a button to trigger a method called process()
in the component class:
<button wire:click="process">Process</button>
</table>
</div>
Then, in its class, we pass $properties
containing row data to the view, and declare process()
as a public method:
public class MyTable
{
// The method to process our rows
public function process(){}
public function render()
{
// Pass properties for sale data to the view
return view('livewire.my-table',[
'properties'=>
Property::paginate(10)
]);
}
}
And now we have our table, checkboxed rows, and Process button!
Wiring Checkboxes
Our table has a bunch of checkboxes, one for each row of data in the $properties
array. Each checkbox represents its row’s ID, retrieved from $prop->id
.
When the user clicks on the “Process” button, we want to send every selected checkbox’s ID to the server.
So, in the component’s class, we’ll declare a public array public $ids = [];
.
Then, we’ll wire each checkbox to the array’s index at $prop->id
—the checkbox’s ID:
We’ll use the row’s id, “$prop->id” to uniquely identify each checkbox.
@foreach( $properties as $prop )
<input type="checkbox"
+ wire:model="ids.{{$prop->id}}" >
With the setup above, if we were to select any checkbox, and do a dd( $this->ids )
in the process()
method, we’ll get ids
as an array of trues
, with each index representing an ID of a checkbox checked by the user:
array:2 [▼ // app/Livewire/MyTable.php
11 => true // 11 and 12 as selected ids
12 => true
]
Streaming Updates with wire:stream
Now that we’re able to get the IDs of each row a user selects, it’s time to process these, and show in the UI as each row goes through the different statuses of processing: “WIP”, and finally, “Processed”.
First, we’ll have to associate a status
attribute per item in the $ids
array. At the current structure of this array, however, we can’t simply add a status
attribute an item. We’ll have to first revise the array’s structure so it can hold an array of key-value pairs.
We can do this re-structuring by revising the wiring of each row:
@foreach( $properties as $prop )
<input type="checkbox"
+ wire:model="ids.{{$prop->id}}.checked" >
Notice we simply add a checked
attribute. This will assign an array of key-value pairs to each row, the first item key being the checked
key:
// New structure!
array:2 [▼ // app/Livewire/MyTable.php:14
11 => ['checked'=>true],
12 => ['checked'=>true]
]
This structure will now allow us to add other attributes later on, just like the status
attribute we’ve mentioned above.
Next, let’s check out how we can show progress as each row completes processing.
Streaming with wire:stream
Livewire v3 now offers the incredible wire:stream
directive. We can “stream” progress as each row completes processing.
In fact, we can even stream a status even before we start processing an item. We can say a row is WIP by updating $this->ids[]['status']
to “WIP”, then “streaming” this update to the client:
to
represents the wire:stream
block we want to stream the update to,
content
represents the public attribute we want to show the update of,
and replace
tells Livewire that we want to replace the previous content
foreach( $this->ids as $id=>$idDetails ){
// Update status to WIP
$this->ids[ $id ][ 'status'] = 'WIP';
// Stream this change to the client
$this->stream(
to: 'process-stream'.$id,
content: $this->ids[ $id ][ 'status'],
replace: true
);
// Processing might take 3 seconds...
sleep( 3 );
}
This stream can now be received in the view by setting up a matching receiver:
@foreach( $properties as $prop )
<tr>
<td>
+ <td wire:stream="process-stream.{{ $prop->id }}">
+ {{ $content }}
</td>
</tr>
@endforeach
Then, once an item completes processing, we can update the status
attribute to “Processed” and finally stream this change to the UI again:
foreach( $this->ids as $id=>$idDetails ){
// Before processing stream here...
// Processing takes up to 3 seconds
sleep(3);
// Completed update and stream
$this->ids[ $id ][ 'status' ] = 'Processed!';
+ $this->stream(
+ to: 'process-stream'.$id,
+ content: $this->ids[$id]['status'],
+ replace: true
+ );
}
And that’s it! We now have a bunch of selectable rows, we can send them to the server for processing, and we get to update each row in the client table, real time!