skip to content

In Livewire, how does one component notify another with dispatch() and #[On], and how do to() and self() narrow who receives the event?

level: middleimportance: must knowfreq 60%

answer

  1. $this->dispatch('name', key: value)
  2. #[On('name')] above a public method
  3. named params match listener arguments
  4. ->to('cart-counter') or ->self()
  5. listener runs in a second request

basics

~20 s

$this->dispatch('cart-updated', count: 3) queues a browser event returned with the response; any component with #[On('cart-updated')] on a public method runs it in a follow-up request, with count passed by name. ->to('cart-counter') targets one component; ->self() keeps it on the sender.

solid answer

~40 s

`$this->dispatch('cart-updated', count: 3)` does not call anything on the server directly: the event travels back with the response as an effect, Livewire fires it as a browser event, and every component on the page with `#[On('cart-updated')]` on a public method then makes a **follow-up** request to run that method, receiving `count` as a named argument. `->to('cart-counter')` (or `->to(component: CartCounter::class)`) delivers it only to that component, and `->self()` only to the component that dispatched it. Listeners can use dynamic names such as `#[On('cart-updated.{cart.id}')]`, and a parent can listen on one child with `<livewire:cart-line @saved="$refresh" />`. From Blade, `$dispatch()` and `$dispatchTo()` do the same in the browser. `dispatch()` replaced Livewire 2's `emit()`/`emitTo()`, which no longer exist.

code

php · 23 lines
php
<?php

use App\Support\Cart;
use Livewire\Attributes\On;
use Livewire\Component;

new class extends Component {
    public int $count = 0;

    public function mount(): void
    {
        $this->count = Cart::for(auth()->user())->count();
    }

    #[On('cart-updated')]
    public function refreshCount(int $count): void
    {
        $this->count = $count;
    }
};
?>

<span>Cart ({{ $count }})</span>

go deeper

for a junior

Recall dispatch() with named params, #[On] on a public method, and that emit() is gone.

for a middle

Explain the round trip: events come back with the response, fire in the browser, and listeners run in a follow-up request; use to() and self() to narrow.

for a senior

Count the requests an event fan-out causes, choose targeted or dynamic events, and prefer $parent or reactive props where coupling is fine.

for a principal

Define event naming and ownership conventions so pages with many components stay predictable and do not trigger cascades of round trips.

## Why components need events In Livewire, **every component is independent**: an update to one component sends only that component's state to the server and re-renders only that component. So when a product card adds an item to the cart, the **cart counter in the header**, a separate component, has no idea anything changed. Events are the loosely coupled way to tell it. ## Dispatching and listening ```php // Product card: an action dispatches after changing the cart public function addItem(Product $product): void { Cart::for(auth()->user())->add($product); $this->dispatch('cart-updated', count: Cart::for(auth()->user())->count()); } ``` ```php use Livewire\Attributes\On; // Header cart counter public int $count = 0; #[On('cart-updated')] public function refreshCount(int $count): void { $this->count = $count; } ``` - `dispatch()` takes the event name and **named parameters**; the listener method receives them **by parameter name**. - `#[On('...')]` (import `Livewire\Attributes\On`) registers a public method as a listener. A component only reacts to events it has registered; an unregistered name fails with an event-handler-does-not-exist error. ## What actually happens, step by step 1. The product card's request runs `addItem()`. `dispatch()` records the event; nothing is called yet. 2. The response carries the card's new HTML **and** a list of dispatched events. 3. In the browser, Livewire fires each event as a DOM event that bubbles from the dispatching component up to the window. 4. The cart counter, which registered `cart-updated` when it mounted, sends a **follow-up update** to the server with a call to its listener. 5. The counter's `refreshCount()` runs and the counter re-renders. So one user click costs **at least two round trips**: the dispatching component's, then a follow-up in which each listening component is rebuilt, runs its listener and re-renders. That is the price of loose coupling, and it is why events are for cross-component signals, not for work inside one component. ## Narrowing the audience | Form | Who receives it | |---|---| | `$this->dispatch('cart-updated')` | Every listening component on the page | | `$this->dispatch('cart-updated')->to('cart-counter')` | Only the `cart-counter` component (a name or a class) | | `$this->dispatch('cart-updated')->to(component: CartCounter::class)` | Same, with the named argument | | `$this->dispatch('cart-updated')->self()` | Only the component that dispatched it | Targeting prevents unrelated components that happen to use the same event name from reacting, and saves their round trips. ## Other ways to dispatch and listen - **Dynamic names**: `#[On('cart-updated.{cart.id}')]` listens only for the event suffixed with that component's `$cart->id`, useful when many instances are on a page. - **Listening on one child**: `<livewire:cart-line @saved="$refresh" />` in a parent calls the parent's `$refresh` when that child dispatches `saved`; `@saved="recalc($event.detail.lineId)"` forwards data. - **From Blade**: `wire:click="$dispatch('cart-updated')"` and `$dispatchTo('cart-counter', 'cart-updated', { count: 3 })` fire events straight from the browser without a server action first. - **From JavaScript**: because they are browser events, Alpine and plain scripts can listen and dispatch too; that interop is a topic of its own. ## Version note Livewire 2 used `$this->emit()`, `emitTo()` and `emitSelf()` with a `$listeners` array. Livewire 3 replaced them with `dispatch()` and `#[On]`, and Livewire 4 keeps that API. Code or answers using `emit` describe an API that no longer exists. ## When not to use events - To call the parent from a child that always lives in that parent, `$parent.method()` is simpler. - To keep a child in sync with a value the parent owns, a reactive prop is more direct. - For updates that must reach other users or tabs, browser events are not enough; that needs broadcasting, which is a separate system.

  • Why does a Livewire event listener cost an extra network request?
    Dispatched events are returned to the browser as part of the dispatching component's response and only then fired as DOM events. Each listening component must then send an update to the server to run its listener, because its state lives in its own snapshot. One click therefore costs the original round trip plus a follow-up, and every listener is rebuilt and re-rendered.
  • How do you make many cart-line components on one Livewire page each react only to their own event?
    Use a dynamic listener name that embeds a property, such as `#[On('line-updated.{line.id}')]`, and dispatch `line-updated.{$id}` from the sender. Only the instance whose `$line->id` matches receives it. Alternatively, target a single component name with `->to()`.
  • What replaced Livewire 2's emitTo('cart-counter', 'cart-updated')?
    `$this->dispatch('cart-updated')->to('cart-counter')` on the server, or `$dispatchTo('cart-counter', 'cart-updated')` from Blade. Livewire 3 removed `emit`, `emitTo` and `emitSelf`, and Livewire 4 keeps the `dispatch()` API.

saying these in an interview costs you the question

  • dispatch() calls the listener methods immediately in the same PHP request
  • Livewire 4 components still use $this->emit() and emitTo()
  • Event parameters arrive as one array in the first argument, never by name
  • Any public method runs for any event name the browser sends
  • An event dispatched with ->self() is also heard by the parent
  • Events reach other users' browsers without broadcasting