Courses

Livewire 3 From Scratch: Practical Course

Multiple Components: Dispatching Events

Summary of this lesson:
- Implementing event dispatching between components
- Using #[On] attribute for event listening
- Updating component state based on events
- Setting up relationships for connected components

There are a lot of cases where one Livewire component needs to communicate with another. In this lesson, let's see how it is done using Events.


Initial Components

In this example, we will have a Livewire component for showing a post and another component for posting a comment. When a comment is posted, the comment count in the ViewPost Livewire component should be updated.

DB structure: the Comment Model only has the post_id and body fields. And we need a comments() relationship in the Post Model.

database/migrations/xxxx_create_comments_table.php:

Schema::create('comments', function (Blueprint $table) {
$table->id();
$table->foreignId('post_id');
$table->string('body');
$table->timestamps();
});

app/Models/Post.php:

use Illuminate\Database\Eloquent\Relations\HasMany;
 
class Post extends Model
{
protected $fillable = [
'title',
'body',
];
 
public function comments(): HasMany
{
return $this->hasMany(Comment::class);
}
}

And here is the initial Livewire components.

app/Livewire/ViewPost.php:

use App\Models\Post;
use Illuminate\Contracts\View\View;
 
class ViewPost extends Component
{
public Post $post;
 
public int $commentsCount = 0;
 
public function mount(Post $post): void
{
$this->post = $post;
$this->post->loadCount('comments');
$this->commentsCount = $this->post->comments_count;
}
 
public function render(): View
{
return view('livewire.view-post');
}
}

resources/views/livewire/view-post.php:

<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 text-gray-900 dark:text-gray-100">
 
<h1 class="text-lg font-bold mb-4">{{ $post->title }}</h1>
 
<p>Comments: {{ $commentsCount }}</p>
 
<livewire:create-comment :post="$post" />
</div>
</div>
</div>
</div>

app/Livewire/CreateComment.php:

use App\Models\Post;
use Livewire\Component;
use Livewire\Attributes\Validate;
use Illuminate\Contracts\View\View;
 
class CreateComment extends Component
{
public Post $post;
 
#[Validate('required')]
public string $body = '';
 
public function save(): void
{
$this->validate();
 
$this->post->comments()->create(['body' => $this->body]);
 
$this->reset('body');
}
 
public function render(): View
{
return view('livewire.create-comment');
}
}

Notice: As you can see in the CreateComment Livewire component, no mount() method exists. This is another alternative to reduce code. Livewire automatically sets $post to the correct Post from the Route Model Binding.

resources/views/livewire/create-comment.php:

<div class="mt-4">
<form method="POST" wire:submit="save">
<div>
<label for="body" class="block font-medium text-sm text-gray-700">Body</label>
<textarea id="body" wire:model="body" class="block mt-1 w-full border-gray-300 rounded-md shadow-sm"></textarea>
@error('body')
<span class="mt-2 text-sm text-red-600">{{ $message }}</span>
@enderror
</div>
 
<button class="mt-4 px-4 py-2 bg-gray-800 rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-gray-700">
Save
</button>
</form>
</div>

I will use it as a full-page Livewire component, so the Route would look like this.

routes/web.php:

Route::get('post/{post}', \App\Livewire\ViewPost::class);

After visiting the page, we have a similar result to the one below:

initial post view


Dispatching and Listening to the Events

After saving the comment, we need to trigger an event. This is done using the dispatch() method and passing the event's name.

app/Livewire/CreateComment.php:

class CreateComment extends Component
{
// ...
 
public function save(): void
{
$this->post->comments()->create(['body' => $this->body]);
 
$this->dispatch('comment-added');
 
$this->reset('body');
}
 
// ...
}

Notice: in the older Livewire v2, the method name was called ->emit(), in v3 it was changed to ->dispatch().

Now that event is triggered, we must listen for it in the ViewPost Livewire component. To do that, we need to add a #[On] attribute above the method that needs to be called.

app/Livewire/ViewPost.php:

use Livewire\Attributes\On;
 
class ViewPost extends Component
{
// ...
 
#[On('comment-added')]
public function commentAdded(): void
{
$this->commentsCount++;
}
 
// ...
}

After saving the comment now, the comments count is incremented.

event is triggered

So, that's how you communicate between Livewire components: your page may consist of many individual smaller components, passing data between each other by dispatching events.

Previous: Property Attributes: Locked, URL, Computed
avatar

before Dispatching and Listening to the Events

my code return an error saying Unable to find component: [create-comment]

avatar

I am really stuck here is my code

web.php

Route::view('posts/create', 'posts.create');

Route::get('post/{post}', \App\Livewire\ViewPost::class);

CreateComment.php

<?php

use App\Models\Post;
use Livewire\Component;
use Illuminate\Contracts\View\View;
use Livewire\Attributes\Rule;
 
class CreateComment extends Component
{
    public Post $post;
 
    #[Rule('required')]
    public string $body = '';
 
    public function save(): void
    {
        $this->post->comments()->create(['body' => $this->body]);
 
        $this->reset('body');
    }
 
    public function render(): View
    {
        return view('livewire.create-comment');
    }
}

create-comment.blade.php

<div class="mt-4">
    <form method="POST" wire:submit="save">
        <div>
            <label for="body" class="block font-medium text-sm text-gray-700">Body</label>
            <textarea id="body" wire:model="body" class="block mt-1 w-full border-gray-300 rounded-md shadow-sm"></textarea>
            @error('body')
                <span class="mt-2 text-sm text-red-600">{{ $message }}</span>
            @enderror
        </div>
 
        <button class="mt-4 px-4 py-2 bg-gray-800 rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-gray-700">
            Save
        </button>
    </form>
</div>

ViewPost.php

<?php

namespace App\Livewire;

use Livewire\Component;
use App\Models\Post;
use Illuminate\Contracts\View\View;

class ViewPost extends Component
{
    public Post $post;
 
    public int $commentsCount = 0;
 
    public function mount(Post $post): void
    {
        $this->post = $post;
        $this->post->loadCount('comments');
        $this->commentsCount = $this->post->comments_count;
    }
    
    public function render()
    {
        return view('livewire.view-post');
    }
}

view-post.blade.php

<div class="py-12">
    <div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
        <div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
            <div class="p-6 text-gray-900 dark:text-gray-100">
 
                <h1 class="text-lg font-bold mb-4">{{ $post->title }}</h1>
 
                <p>Comments: {{ $commentsCount }}</p>
 
                <livewire:create-comment :post="$post" />
            </div>
        </div>
    </div>
</div>

Post Model

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;

class Post extends Model
{
    use HasFactory;

    protected $fillable = ['title', 'body'];

    public function comments(): HasMany 
    {
        return $this->hasMany(Comment::class);
    } 
}

I do not know what am I doing wrong

avatar

ok, so I was missing the namespace line in the CreateComment component

namespace App\Livewire;

and also the full page components need to have a Global layout configuration

more details can be found here https://livewire.laravel.com/docs/components#layout-files

avatar

Yes, quite often we don't provide the full file with namespace as we assume it should be pretty obvious to the users, as we focus on the main code around the main topic. But maybe we should make some exceptions sometimes. Glad you figured it out!

avatar
You can use Markdown
avatar

I wish you wood include the code to create the pages you wont us to work in such as the app/Livewire/ViewPost.php:,
resources/views/livewire/view-post.blade.php, app/Livewire/CreateComment.php: and app/Livewire/CreateComment.php resources/views/livewire/create-comment.blade.php: some of us may not be sure how to create these pages.

php artisan make:livewire ViewPost

php artisan make:livewire CreateComment

👍 1
avatar
You can use Markdown
avatar

Don't forget to validate in the save method of app/Livewire/CreateComment.php:

public function save(): void
{
    $this->validate(); // <--- HERE

    $this->post->comments()->create(['body' => $this->body]);
 
    $this->reset('body');
}
👍 1
avatar
You can use Markdown
avatar
You can use Markdown