skip to content

Callable Actions & Dispatch

wire:click and wire:submit call public component methods, while dispatch() and #[On] let components signal each other. Interviewers probe how data flows between parent and child components.

on this pageshow

explore

questions

5

In Livewire, how do wire:click and wire:submit call component methods, and how are arguments passed to those methods?

level: juniorimportance: must knowfreq 65%

answer

  1. public method name in the directive
  2. arguments are JavaScript expressions
  3. wire:submit prevents default itself
  4. form inputs disabled while in flight
  5. type-hinted model resolved from its id

basics

~20 s

wire:click="addItem(42)" and wire:submit="checkout" send a request that calls that public method on the Livewire component. Arguments are JavaScript expressions sent as JSON; type-hinted models are resolved from ids and container services are injected. wire:submit prevents the browser submit itself.

solid answer

~40 s

`wire:click="addItem({{ $product->id }}, 2)"` tells Livewire to send a request that calls the **public** method `addItem()` on the component, with the arguments evaluated in the browser as JavaScript and sent as JSON. On the server, a type-hinted model parameter (`Product $product`) is resolved from the id, and container services such as a repository are injected before the passed values. `wire:submit="checkout"` on a `<form>` intercepts the submit event and calls `preventDefault()` itself, so no `.prevent` is needed, and it disables the form's inputs and submit button while the request runs. After the method returns, the component re-renders and the new HTML is morphed into the page. Because any public method can be called this way, arguments are untrusted input, just like request data.

code

html · 13 lines
html
<div>
    @foreach ($this->products as $product)
        <div wire:key="p-{{ $product->id }}">
            {{ $product->name }}
            <button wire:click="addItem({{ $product->id }}, 1)">Add</button>
        </div>
    @endforeach

    <form wire:submit="applyCoupon">
        <input type="text" wire:model="coupon">
        <button type="submit">Apply</button>
    </form>
</div>

go deeper

for a junior

Recall the directive syntax with arguments, that the method must be public, and that wire:submit prevents the default submit.

for a middle

Explain how arguments are evaluated in the browser, sent as JSON, and resolved to models or services on the server.

for a senior

Treat every public method and argument as an endpoint and input, keep helpers protected, and use type hints to constrain what arrives.

for a principal

Set conventions so component actions stay thin, delegate to services, and are reviewed like controller endpoints.

## Actions: public methods the browser can call An **action** in Livewire is a **public method** on the component class that the browser can invoke. Directives such as `wire:click` and `wire:submit` wire DOM events to actions: ```html <button wire:click="addItem({{ $product->id }}, 2)">Add two</button> <form wire:submit="checkout"> <input wire:model="coupon"> <button type="submit">Pay</button> </form> ``` When the event fires, Livewire sends a request carrying the component's snapshot plus a list of **calls** (method name and arguments). The server rebuilds the component, runs the method, re-renders the Blade view and returns the new HTML and state, which the browser morphs into the page. ## How arguments travel - The text inside the parentheses is a **JavaScript expression** evaluated in the browser. Blade's `{{ $product->id }}` is printed into it on the server first, so the browser sees `addItem(42, 2)`. - Strings need quotes: `wire:click="setSize('large')"`. - `$event` gives access to the DOM event, for example `wire:keydown.enter="search($event.target.value)"`. - Values are sent as JSON, so they arrive in PHP as strings, numbers, booleans, arrays or null, never as PHP objects. ## What PHP does with them The method's parameters are filled in order, with two conveniences: 1. **Model resolution**: a parameter type-hinted with an Eloquent model, `public function addItem(Product $product, int $qty)`, is looked up from the passed id, much like route model binding. 2. **Dependency injection**: parameters type-hinted with a class from the service container, `public function checkout(CartService $cart)`, are resolved by the container; the values from the browser fill the remaining parameters. ## wire:submit specifics | Behaviour | `wire:click` | `wire:submit` | |---|---|---| | Needs `.prevent` to stop default | Only for links and submit buttons | No, calls `preventDefault()` itself | | Disables form inputs while running | No | Yes, inputs and submit button inside the form | | Typical use | Buttons, toggles, row actions | Saving a whole form | `wire:submit` disables the form's controls during the request, which stops a double submit on a slow connection. ## Other listeners and modifiers `wire:` accepts any browser event name (`wire:keydown`, `wire:mouseenter`, `wire:change`), and modifiers such as `.prevent`, `.stop`, `.once`, `.debounce`, `.window`, `.outside` and key aliases like `.enter` or `.escape` come from Alpine's `x-on`, which Livewire uses underneath. ## What cannot be called - **Protected and private methods** cannot be called; the request fails with a method-not-found error. - `render()` is excluded even though it is public. - A method marked `#[Computed]` is read-only and throws if called as an action. Everything else that is public **can** be called from the browser's devtools, whether or not the template references it. That is why action parameters are treated as untrusted input and why helpers that must not be reachable are kept protected. ## Ending an action An action usually just changes state and lets the component re-render. It can also: - **Redirect**: `return redirect()->to('/cart');` or `$this->redirect('/cart')`; the browser navigates after the response. - **Return a value**: the value is sent back with the response and is available to JavaScript that called the action; a `wire:click` in Blade simply ignores it. - **Dispatch events** for other components, as in the example below. - **Throw**: a validation exception fills the error bag and the view shows errors; other exceptions surface like any Laravel error. ## A cart example On a product page, an add-to-cart button calls an action on the product card component: ```php public function addItem(Product $product, int $qty = 1): void { Cart::for(auth()->user())->add($product, $qty); $this->dispatch('cart-updated'); } ``` The action runs on the server, the card re-renders, and the dispatched event lets a separate cart counter in the header refresh itself.

  • In a Livewire action, what happens if the method is protected instead of public?
    It cannot be called from the browser. Livewire only dispatches calls to public methods defined on the component (excluding `render()`), so a call to a protected method fails with a method-not-found error. That is the standard way to keep internal helpers out of reach of devtools.
  • Why does a Livewire action receive "42" as a string when a method parameter is untyped?
    Arguments are sent as JSON from a JavaScript expression. `addItem({{ $id }})` prints a number, but values read from inputs or `$event.target.value` are strings. Add scalar type hints such as `int $qty` so PHP coerces them, and validate them as you would request input.

saying these in an interview costs you the question

  • wire:submit needs .prevent or the browser posts the form
  • Protected methods can be called from wire:click
  • Only methods referenced in the template can be called
  • Arguments arrive as PHP objects exactly as they were in Blade
  • wire:click reloads the page to run the method
open as a page

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%

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.

open as a page

In Livewire, what do the magic actions $refresh, $set, $toggle and $parent do when used in a wire:click directive?

level: juniorimportance: should knowfreq 40%

basics

~20 s

Livewire's magic actions run without a method of your own: $refresh re-renders the component, $set('prop', value) assigns a public property, $toggle('prop') flips a boolean, and $parent.remove(3) calls an action or reads a property on the parent component.

open as a page

In Livewire, how do the #[Reactive] and #[Modelable] attributes differ when a parent and a nested child component share a value?

level: middleimportance: should knowfreq 38%

basics

~20 s

#[Reactive] makes a Livewire child property follow the value the parent passes on each parent update, one way; the child may not change it. #[Modelable] lets the parent put wire:model on the child tag, syncing both ways.

open as a page

In Livewire 4, when should an action use #[Renderless], skipRender() or #[Async], and what goes wrong when an #[Async] action changes component state?

level: seniorimportance: should knowfreq 25%

basics

~20 s

#[Renderless] or skipRender() skip re-rendering after an action the view does not reflect, such as logging. Livewire 4's #[Async] runs an action in parallel instead of queued; if it mutates state, parallel requests share a snapshot and lose updates.

open as a page