13

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?

Qirel
  • 25,449
  • 7
  • 45
  • 62

2 Answers2

42

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:

Qirel
  • 25,449
  • 7
  • 45
  • 62
  • Really Really Thank you! AM I need to install Websockets ? or Laravel Echo ? –  Oct 03 '20 at 17:58
  • If you want to use websockets, then they integrate with Livewire beautifully. Alternatively you need to use polling (which makes Livewire re-render the component every X seconds), I can update the answer to include an example of that too. – Qirel Oct 03 '20 at 18:00
  • No I don't want to use websockets, I mean by. my Q is if I have to install websockets then I will follow the first way and if I'm not, I will use the second way that you mention ! –  Oct 03 '20 at 18:02
  • There are pros and cons to both approaches -- polling can be much easier to integrate, but it means there's more requests fired to your server. Doesn't necessarily have to be of much impact, but its something you have to be aware of (you can inspect the networks tab in your browser to see them being fired). – Qirel Oct 03 '20 at 18:05
  • But polling will continue refreshing after the queue is done! –  Oct 03 '20 at 18:07
  • Yes, polling means that the page will always, for as long as the page is open, refresh itself after X amount of seconds (default is 2, but I've shown how you set it to 5 seconds, for example). – Qirel Oct 03 '20 at 18:07
  • Last thing! Now If I follow the last way (Broadcasting) I have to use also websockets or pusher with it ? right? or just laravel echo do the job? –  Oct 03 '20 at 18:13
  • 1
    Laravel Echo is a websocket, pusher is a separate package but is quite handy to use in combination with Echo. Follow the manual https://laravel.com/docs/8.x/broadcasting#installing-laravel-echo – Qirel Oct 03 '20 at 18:16
0

The easier way is to just call the mount function like so:

public function mount()
{
    //some code to run on mount
}

public function refreshComponent()
{
    $this->mount();
}
Lajos Arpad
  • 64,414
  • 37
  • 100
  • 175