Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

bvfi-dev's avatar

Dealing with Livewire 3's async Event communication between components

I have 2 Components, the parent one is Listing\Create and the child one is ImageUpload. They communicate with each other using Events. The problem is, there is no way to indicate that I want the event to finish before continuing, and since they work asynchronously, I need a way to indicate that they need to be waited on. I've been at it for hours, so maybe I need to rethink the whole logic, but I've found this way to be the most cleanest and easiest for me. Summarized Listing\Create:

class Create extends Component
{
    public $isImageValidated = false;
    #[On('validate-image-fields')]
    public function isImageValidated($isVerified)
    {
        $this->isImageValidated = $isVerified;
        \Debugbar::addMessage($this->isImageValidated, 'listing-var');
    }
    public function save()
    {
        \Debugbar::addMessage('Saving Listing', 'listing1');
        $this->dispatch('validate-image')->to(ImageUpload::class); //1st event
        $this->validate();
        \Debugbar::addMessage($this->isImageValidated, 'listing2');
        if($this->isImageValidated) { //This variable can only be changed through the isImageValidated(boolean) function
            ...

Then there is the child component:

class ImageUpload extends Component
{
    public $isVerified = false;
    protected function rules()
    {
        return [
            'image' => 'nullable|image|mimes:jpg,jpeg,bmp,png,webp|max:2048',
            'alt' => 'nullable|string|max:200',
        ];
    }
    #[On('validate-image')]
    public function updatedImage(): void
    {
        \Debugbar::addMessage('Validating image...', 'validate1');
        $rules = $this->rules();
        $validator = Validator::make([
            'image' => $this->image,
            'alt' => $this->alt,
        ], $rules);
        if($validator->fails()) {
            $this->removeImage(); //Function that handles the removing of the image preview
            $this->isVerified = false;
        } else {
            $this->isVerified = true;
        }
        \Debugbar::addMessage($this->isVerified, 'validate2');
        $this->dispatch('validate-image-fields', isVerified: $this->isVerified)->to(Create::class); //2nd event
    }

The output from Debugbar is:

update#3 (ajax):
Saving Listing[listing1]
false[listing2] //Here's the problem, it doesnt wait for the event in the child component to complete and set the $this->isImageValidated, so it stays false.
--------------------
update#4 (ajax):
Validating image....[validate1]
true[validate2]
--------------------
#5 update(ajax):
true[listing-var]

I understand why it's not working. It first executes the whole save() code, and it doesnt wait for the validate-image event to complete. The Livewire documentation doesn't address this, nor does it mention that the events are asynchronous, which led me to writing the code the way I did. I figured it doesn't continue, until the event function is fully completed, because that makes more sense, but oh well. What are the workarounds and the best practices for this?

0 likes
1 reply
bvfi-dev's avatar
bvfi-dev
OP
Best Answer
Level 3

The correct way was to split the function into multiple little functions and jump between them with dispatch. Leaving some code in case someone faces the same noob scenario. In my save() function I added $this->dispatch('validate-image', toSave: true)->to(ImageUpload::class); And then I restructured my ImageUpload like:

public function updatedImage(): void
    {
        if (!$this->_validateImage()) $this->removeImage();
    }

    #[On('validate-image')]
    public function saveObject(): void
    {
        if ($this->_validateImage()) {
            $this->dispatch('save-object');
        } else {
            $this->removeImage();
        }
    }

    /**
     * Validates the image & alt attribute
     * @return bool True if it passes | False if it doesn't
     */
    private function _validateImage(): bool
    {
        $rules = $this->rules();
        $validator = Validator::make([
            'image' => $this->image,
            'alt' => $this->alt,
        ], $rules);

        return !$validator->fails();
    }

Which then dispatches to:

#[On('save-object')]
    public function saveListing()
    {
        $user_id = Auth::user()->id;
        ...

Like the Romans said "Divide and conquer"

Please or to participate in this conversation.