How to refresh the component after the queue job is finished?

ali picture ali · Oct 3, 2020 · Viewed 11k times · Source

I have a table products and job_statuses using this package: laravel-job-status

There is a column in job_statuses called status the package made this column finished once the queue job is finished!

So I created a column in products table called job_status_id (relation with job_statuses) just to save the job status id in the products table to see if this job is finished!

Simply I created a component using Livewire for just refresh single product when the job finished refresh the component:

class ProductIndex extends Component
{
    public $product;


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

    public function render()
    {
        return view('livewire.merchant.product.index');
    }
}

Inside product-index component:

@if ($product->job_status->status == 'finished')
  // show real image
 
@else
  // show loader 
@endif

My blade:

@foreach ($products as $product)
  <livewire:merchant.product.product-index :product="$product" :key="$product->id">
@endforeach

How can refresh the product component if the status is finished?

Answer

Qirel picture Qirel · Oct 3, 2020

You can add an event-listener in your component, then fire an event from anywhere on the page - even from JavaScript - to refresh the component.

To add a listener to make the component refresh itself, simply add the following line to your ProductIndex component.

protected $listeners = ['refreshProducts' => '$refresh'];

Livewire will now listen for any refreshProducts events that are emitted, and once they are emitted, it will refresh that component.

You can also customize it further, by replacing the magic $refresh action with a method, which you can pass parameters to. If you name the event the same as the method, you don't need to specify a key/value pair (eventname as the key, methodname as the value), and the value alone will suffice. Here's an example,

protected $listeners = ['refreshProducts'];

// ...

public function refreshProducts($product_id = null) 
{
    // Refresh if the argument is NULL or is the product ID
    if ($product_id === null || $product_id === $this->product->id) {
        // Do something here that will refresh the event
    }
}

From within a Livewire component, you can emit events using

$this->emit('refreshProducts');` 

// or if you are passing arguments, as with the second example, 
$this->emit('refreshProducts', $productID);

You can also emit events from JavaScript, by doing

<script>
    Livewire.emit('refreshProducts')
</script>

If you want to make the queue trigger that event once its complete, you need to implement something that either polls the server to ask "has the job finished" and then fire the event, or you can use Laravel Echo as a websocket. This will allow you to fire and listen for events from outside the Livewire ecosystem.

Polling

When you're polling, you don't have to emit an event for every update, as the component will refresh itself continuously .

Polling is the easiest way to continuously update a Livewire component, it does not require any websockets integration like Laravel Echo. This means that every X seconds (default is 2 seconds), your component is going to make an AJAX request to your server, to fetch its newest data, and re-render itself with the new data.

This is easily achieved by wrapping the component with the wire:poll attribute - here's an example of using 5 seconds.

<div wire:poll.5s>
    <!-- The rest of your view here -->
</div>

However, this means that all instances of that component will re-render themselves and fire an AJAX request of their own to the server to get the newest data. You might want to make a "parent" component for all your items, thereby just having 1 singular component re-render.

Broadcasting & Websockets

I'm going to assume that you have installed Laravel Echo already. Now - to achieve this functionality of broadcasting, you first need to create an event. Run the command

php artisan make:event ProductStatusUpdated

This will create an event in your app\Events folder. Customize it to however you need it. It will look something like this after running the command,

class ProductStatusUpdated
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    /**
     * Create a new event instance.
     *
     * @return void
     */
    public function __construct()
    {
        //
    }

    /**
     * Get the channels the event should broadcast on.
     *
     * @return \Illuminate\Broadcasting\Channel|array
     */
    public function broadcastOn()
    {
        return new PrivateChannel('channel-name');
    }
}

We can change the channel from PrivateChannel('channel-name') to Channel('products'), this allows us to listen for it in Livewire in the products channel (you could name the channel whatever you want, and you can also listen for private events - there's documentation for that in the Livewire documentation, referenced at the bottom of this answer).

So that means the broadcastOn would look something like this

public function broadcastOn()
{
    return new Channel('products');
}

Next, after the job has completed its work and all the statuses has been set, fire that event from Laravel, using

event(new \App\Events\ProductStatusUpdated);

Now we need to update our listener in the Livewire component, so that we can actually listen for the broadcast on that channel through Laravel Echo (and not the Livewire events that we did before).

protected $listeners = ['echo:products,ProductStatusUpdated' => 'refreshProducts'];

And we're done! You're now using Laravel Echo to broadcast an event, which Livewire intercepts, and then runs. The listener is now a key/value pair, where the value is still the method name in the component (you can still use the magic action $refresh instead if you desire to), and the key is channel,event prefixed by echo:.

Resources: