Livewire 3 — complete guide

Livewire lets you build dynamic interfaces in Laravel with PHP and Blade — no separate frontend application and no API layer. This guide runs from installation to production: components, state, forms, events, file uploads, testing, security and performance.

Livewire 3.x Laravel 10 / 11 / 12 PHP 8.1+ 24 chapters

1. What Livewire is

Livewire is a full-stack framework for Laravel that lets you build dynamic interfaces in PHP and Blade without leaving the backend. You do not write a separate SPA, you do not stand up a REST or GraphQL layer, and you do not maintain a second data model in JavaScript.

How it works

A Livewire component is a PHP class plus a Blade view. On the first page load the component renders server-side as ordinary markup. After that a small JS runtime intercepts user interactions (click, input, submit), sends an AJAX request carrying the component's current state, the server re-renders the component and returns fresh markup. The runtime diffs the old and new DOM trees and patches only what changed — the page never reloads.

One request cycle
Browser                          Server
   │                                │
   │  click on wire:click="save"    │
   ├───────── POST /livewire/update ┤
   │  { snapshot, calls, updates }  │
   │                                ├── restore component from snapshot
   │                                ├── apply property updates
   │                                ├── call save()
   │                                ├── run render()
   │  { snapshot, html, effects }   │
   ├◄───────────────────────────────┤
   ├── morph the DOM (diffs only)   │
   ▼                                ▼

When Livewire is the right call

  • Admin panels, CRMs, dashboards, internal tools — lots of forms and tables, little complex animation.
  • Forms with dependent fields, multi-step wizards, live validation.
  • Tables with search, filters, sorting and pagination.
  • A team that is strong in PHP and does not want to maintain a separate frontend stack.
  • Shipping features fast without duplicating logic across PHP and JS.

When to reach for something else

  • Interfaces that update constantly: online editors, canvases, drag-and-drop builders, games.
  • Offline-first apps and PWAs with complex local synchronisation.
  • Mobile apps — those need an API, not markup over the wire.
  • Poor or high-latency connections: every cycle is a network round trip.
Rule of thumb: if an interaction needs data from the server, it belongs in Livewire. If it is purely visual (open a menu, switch a tab, show a tooltip), it belongs in Alpine.js on the client with no server call at all.

Versions

This guide covers Livewire 3.x. Requirements: PHP 8.1+, Laravel 10 or newer. Version 3 bundles Alpine.js, renamed the event API (dispatch instead of emit), introduced PHP attributes (#[Computed], #[Validate], #[Url]), and made wire:model deferred by default.

2. Installation and setup

Installing into an existing Laravel project is a single command.

Bash
composer require livewire/livewire

In Livewire 3 you do not have to wire up the assets yourself: the package injects @livewireStyles and @livewireScripts into your layout. With a non-standard layout or a strict CSP you can place the directives manually.

resources/views/layouts/app.blade.php
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>{{ $title ?? 'Application' }}</title>

    <link rel="stylesheet" href="{{ asset('css/app.css') }}">
    @livewireStyles
</head>
<body class="antialiased">

    {{ $slot }}

    @livewireScripts
</body>
</html>
Common mistake. If a component renders but does not react to clicks, the layout is almost always missing @livewireScripts — or it sits before your own Alpine.js build. Livewire 3 already ships Alpine; a second Alpine instance breaks reactivity.

Publishing the config

Bash
php artisan livewire:publish --config

The settings that matter in config/livewire.php:

SettingPurpose
class_namespaceNamespace for component classes. Defaults to App\Livewire.
view_pathDirectory holding component Blade views.
layoutLayout used by full-page components.
temporary_file_uploadDisk, lifetime and rules for temporary uploads.
inject_assetsAutomatic CSS/JS injection. Turn off if you place the directives yourself.
navigate.show_progress_barProgress bar for wire:navigate.

Verifying the install

Bash
php artisan livewire:make Counter
# CLASS: app/Livewire/Counter.php
# VIEW:  resources/views/livewire/counter.blade.php

3. Your first component

A component is two files: a class and a view.

app/Livewire/Counter.php
<?php

namespace App\Livewire;

use Livewire\Component;

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

    public function increment(): void
    {
        $this->count++;
    }

    public function decrement(): void
    {
        $this->count--;
    }

    public function reset(): void
    {
        $this->count = 0;
    }

    public function render()
    {
        return view('livewire.counter');
    }
}
resources/views/livewire/counter.blade.php
<div class="flex items-center gap-3">
    <button type="button" wire:click="decrement" class="btn">−</button>

    <span class="text-2xl font-bold">{{ $count }}</span>

    <button type="button" wire:click="increment" class="btn">+</button>

    <button type="button" wire:click="reset" class="btn btn-ghost">Reset</button>
</div>
Exactly one root element. A component view must have precisely one root element. Comments and bare text at the top level also break the morph algorithm. If you need several blocks, wrap them in a <div>.

Three ways to render a component

Blade
{{-- 1. Tag syntax (recommended) --}}
<livewire:counter />

{{-- 2. Directive --}}
@livewire('counter')

{{-- 3. With parameters --}}
<livewire:counter :start="10" title="Order counter" />

Full-page components

A component can be attached straight to a route and render as a standalone page.

routes/web.php
use App\Livewire\Dashboard;

Route::get('/dashboard', Dashboard::class)->middleware('auth')->name('dashboard');
app/Livewire/Dashboard.php
use Livewire\Attributes\Layout;
use Livewire\Attributes\Title;

class Dashboard extends Component
{
    #[Layout('layouts.app')]
    #[Title('Dashboard')]
    public function render()
    {
        return view('livewire.dashboard');
    }
}

Inline components

For very small components you can return the view as a string and skip the Blade file entirely.

PHP
public function render()
{
    return <<<'BLADE'
        <div>
            <button wire:click="$refresh">Refresh</button>
        </div>
    BLADE;
}

4. Component properties

Public class properties are automatically available in the Blade view and survive between requests. They are the component's state.

PHP
class UserProfile extends Component
{
    public string $name = '';
    public ?int $age = null;
    public array $tags = [];
    public bool $isPublic = false;

    public function render()
    {
        return view('livewire.user-profile');
    }
}

What you can store

State is serialised to JSON between requests, so the set of supported types is limited.

AllowedNot allowed
string, int, float, bool, null Closures
array of scalar values Resources, streams
Eloquent models and model collections Arbitrary objects without Wireable/Synth support
Carbon, DateTime, Stringable, enums Objects holding connections, PDO handles and similar
Eloquent models as properties. A model is stored as an identifier and reloaded from the database on every request. Convenient, but it costs one query per cycle. If you only need a few fields, store scalars instead of the whole model.

Initialisation: mount()

mount() is the component constructor. It runs once, on the first render, and receives the parameters passed from Blade.

PHP
use App\Models\Order;

class OrderCard extends Component
{
    public Order $order;
    public string $mode;

    public function mount(Order $order, string $mode = 'compact'): void
    {
        $this->order = $order;
        $this->mode  = $mode;
    }
}
Blade
<livewire:order-card :order="$order" mode="full" :key="$order->id" />

Protecting properties: #[Locked]

By default the client can change the value of any public property. For identifiers and anything that affects access control that is a hole. The #[Locked] attribute forbids changes from the frontend.

PHP
use Livewire\Attributes\Locked;

class InvoiceEditor extends Component
{
    #[Locked]
    public int $invoiceId;   // the client cannot swap in someone else's id

    public string $comment = '';
}

Properties hidden from JSON

protected and private properties are not serialised and are reset between requests. Use them only for values recomputed in every cycle.

PHP
class Report extends Component
{
    public string $period = 'month';

    // Cleared after every request — do not keep state here.
    protected array $cache = [];
}

5. wire:model and data binding

wire:model binds a form field to a component property. In Livewire 3 the binding is deferred by default: the value does not travel to the server on every keystroke, it goes along with the next action (a click, a submit).

Blade
{{-- Deferred: the value ships with the next request --}}
<input type="text" wire:model="name">

{{-- Immediate: a request on every change --}}
<input type="text" wire:model.live="search">

{{-- 500 ms after typing stops --}}
<input type="text" wire:model.live.debounce.500ms="search">

{{-- At most once every 2 s while typing --}}
<input type="text" wire:model.live.throttle.2s="search">

{{-- Only when the field loses focus --}}
<input type="text" wire:model.blur="email">

{{-- Updates instantly on the client, the server finds out later --}}
<input type="text" wire:model.lazy="draft">

Modifiers

ModifierBehaviourUse it for
Deferred sendOrdinary form fields
.liveRequest on every changeLive search, dependent selects
.blurRequest on focus lossPer-field validation on exit
.debounce.XmsWait for a pause in typingSearch-as-you-type
.throttle.XsAt most once per intervalExpensive queries
.numberCast to a numberNumeric fields
.booleanCast to a booleanYes/no selects
.fillTake the initial value from the markupPre-filled forms

Every field type

Blade
{{-- Text, textarea --}}
<input type="text" wire:model="title">
<textarea wire:model="body"></textarea>

{{-- Single checkbox: bool --}}
<input type="checkbox" wire:model="agreed">

{{-- Checkbox group: array --}}
<input type="checkbox" value="php"  wire:model="skills">
<input type="checkbox" value="js"   wire:model="skills">
<input type="checkbox" value="sql"  wire:model="skills">

{{-- Radio buttons --}}
<input type="radio" value="card" wire:model="payment">
<input type="radio" value="cash" wire:model="payment">

{{-- Select --}}
<select wire:model.live="categoryId">
    <option value="">All categories</option>
    @foreach ($categories as $category)
        <option value="{{ $category->id }}">{{ $category->name }}</option>
    @endforeach
</select>

{{-- Multiple select --}}
<select wire:model="tagIds" multiple>
    @foreach ($tags as $tag)
        <option value="{{ $tag->id }}">{{ $tag->name }}</option>
    @endforeach
</select>

Nested data

Dot notation works for arrays and for model properties alike.

PHP
public array $form = [
    'name'    => '',
    'address' => ['city' => '', 'street' => ''],
];

public Post $post;
Blade
<input wire:model="form.name">
<input wire:model="form.address.city">
<input wire:model="post.title">
Binding to a model. For wire:model="post.title" to work the field must be allowed by a validation rule (rules or #[Validate]) — otherwise Livewire throws. That is the guard against mass assignment.

6. Actions

An action is a public component method called from the view. It replaces the usual fetch() plus a server-side handler.

Blade
{{-- Click --}}
<button wire:click="save">Save</button>

{{-- Form submit (intercepted) --}}
<form wire:submit="save">
    <input wire:model="title">
    <button type="submit">Send</button>
</form>

{{-- Keys --}}
<input wire:keydown.enter="search" wire:keydown.escape="clear">

{{-- Other DOM events --}}
<div wire:mouseenter="preload">…</div>
<select wire:change="applyFilter">…</select>

Parameters

Blade
<button wire:click="delete({{ $post->id }})">Delete</button>
<button wire:click="setStatus('published')">Publish</button>
<button wire:click="move({{ $item->id }}, 'up')">Move up</button>
PHP
public function delete(int $postId): void
{
    $post = Post::findOrFail($postId);

    $this->authorize('delete', $post);   // always check authorisation

    $post->delete();

    $this->dispatch('notify', message: 'Post deleted');
}
Never trust the parameters. Every component method is reachable as a public HTTP endpoint. A client can call delete(999) with any id at all. Checking permissions inside the method is mandatory.

Model binding in parameters

Livewire resolves models from an id the same way the Laravel router does.

PHP
public function archive(Post $post): void
{
    $this->authorize('update', $post);

    $post->update(['archived_at' => now()]);
}

Action modifiers

Blade
{{-- Browser confirmation before sending --}}
<button wire:click="delete" wire:confirm="Delete for good? This cannot be undone.">Delete</button>

{{-- preventDefault / stopPropagation --}}
<a href="#" wire:click.prevent="open">Open</a>
<div wire:click.stop="select">…</div>

{{-- Runs once --}}
<button wire:click.once="init">Initialise</button>

{{-- Only when this exact element was clicked --}}
<div wire:click.self="close">…</div>

Magic actions

ActionWhat it does
$refreshRe-render without changing state
$set('prop', value)Assign a value to a property
$toggle('prop')Flip a boolean property
$dispatch('event')Dispatch a Livewire event
$parent.method()Call a method on the parent component
Blade
<button wire:click="$refresh">Refresh</button>
<button wire:click="$set('tab', 'settings')">Settings</button>
<button wire:click="$toggle('showFilters')">Filters</button>
<button wire:click="$parent.closeModal()">Close</button>

Redirects

PHP
public function store(): void
{
    $this->validate();

    $post = Post::create($this->only('title', 'body'));

    session()->flash('status', 'Post created');

    $this->redirect(route('posts.show', $post), navigate: true);
}

7. The request lifecycle

Knowing the order of execution removes 90% of the "mysterious" bugs. There are two paths: the initial render (an ordinary HTTP page request) and subsequent updates (Livewire AJAX requests).

Initial render

Order
1. The component instance is created
2. boot()
3. mount($params)
4. booted()
5. hydrate hooks do NOT run
6. render()
7. Markup is inserted into the page

Subsequent update

Order
 1. POST /livewire/update arrives with the state snapshot
 2. The snapshot checksum is verified (tamper protection)
 3. The component instance is created
 4. boot()
 5. State is restored from the snapshot
 6. hydrate() and hydrateFoo() for each property
 7. booted()
 8. updating($prop, $value) / updatingFoo($value)   — before assignment
 9. Properties receive their new values
10. updated($prop, $value) / updatedFoo($value)     — after assignment
11. Queued calls (actions) are executed
12. rendering()
13. render()
14. rendered($view, $html)
15. dehydrate() — state is packed back into the snapshot
16. Response: new markup plus effects (events, redirects, browser dispatches)

Snapshot and checksum

The entire component state travels to the client and back inside the wire:snapshot attribute. To stop the client tampering with it, Livewire signs the snapshot with an HMAC derived from APP_KEY. A signature mismatch is rejected with "Livewire encountered corrupt data".

The practical consequence. The more state sits in public properties, the more data crosses the network on every cycle. A collection of 500 models in a public property means megabytes per click. Keep state minimal and fetch lists in render() or via #[Computed].

What happens to the DOM

Livewire does not replace the node wholesale, it performs a morph: it walks the old and the new tree and changes only the differences. That is why input focus, scroll position and Alpine state survive. When the structure of a list changes, the algorithm needs a hint — see wire:key in chapter 10.

8. Lifecycle hooks

Hooks let you step into any phase of the cycle.

PHP
class ProductEditor extends Component
{
    public Product $product;
    public string $name = '';
    public float $price = 0;

    // Runs at the start of EVERY request, before state is restored.
    public function boot(): void
    {
        // A good place for dependencies that cannot be serialised.
    }

    // Only on the first render.
    public function mount(Product $product): void
    {
        $this->product = $product;
        $this->name    = $product->name;
        $this->price   = $product->price;
    }

    // After state is restored, on every subsequent request.
    public function hydrate(): void
    {
    }

    // After boot() and state restoration.
    public function booted(): void
    {
    }

    // Before any property changes.
    public function updating(string $property, mixed $value): void
    {
    }

    // After any property changes.
    public function updated(string $property, mixed $value): void
    {
        $this->validateOnly($property);
    }

    // Only for the $price property.
    public function updatedPrice(mixed $value): void
    {
        $this->price = round((float) $value, 2);
    }

    // For the nested key form.email
    public function updatedFormEmail(mixed $value): void
    {
        $this->form['email'] = strtolower(trim($value));
    }

    public function rendering(): void
    {
    }

    public function rendered(mixed $view, string $html): void
    {
    }

    // Before state is packed into the snapshot.
    public function dehydrate(): void
    {
    }

    public function render()
    {
        return view('livewire.product-editor');
    }
}

Naming rules

PropertyHook
$priceupdatedPrice()
$isActiveupdatedIsActive()
$form['email']updatedFormEmail()
$post->titleupdatedPostTitle()
The standard move. Resetting pagination when a filter changes belongs in a hook: public function updatedSearch() { $this->resetPage(); }

9. Validation and Form objects

Livewire uses Laravel's validator — the same rules, messages and localisation.

Option 1: attributes (Livewire 3)

PHP
use Livewire\Attributes\Validate;

class ContactForm extends Component
{
    #[Validate('required|string|min:2|max:80')]
    public string $name = '';

    #[Validate('required|email:rfc,dns')]
    public string $email = '';

    #[Validate('required|string|min:20|max:2000')]
    public string $message = '';

    #[Validate('accepted', message: 'Please agree to the processing of your data.')]
    public bool $consent = false;

    public function submit(): void
    {
        $data = $this->validate();

        Contact::create($data);

        $this->reset();

        session()->flash('status', 'Message sent');
    }
}

Option 2: the rules() method

Needed when the rules depend on state.

PHP
protected function rules(): array
{
    return [
        'email' => [
            'required',
            'email',
            Rule::unique('users', 'email')->ignore($this->userId),
        ],
        'password' => $this->userId ? 'nullable|min:8|confirmed' : 'required|min:8|confirmed',
    ];
}

protected function messages(): array
{
    return [
        'email.unique' => 'That email address is already registered.',
    ];
}

protected function validationAttributes(): array
{
    return [
        'email' => 'email address',
    ];
}

Live validation

PHP
public function updated(string $property): void
{
    $this->validateOnly($property);   // check only the field that changed
}
Blade
<form wire:submit="submit" novalidate>
    <label for="email">Email</label>
    <input id="email" type="email" wire:model.blur="email"
           class="@error('email') border-red-500 @enderror">

    @error('email')
        <p class="text-sm text-red-600">{{ $message }}</p>
    @enderror

    <button type="submit" wire:loading.attr="disabled">
        <span wire:loading.remove wire:target="submit">Send</span>
        <span wire:loading wire:target="submit">Sending…</span>
    </button>
</form>

Driving errors by hand

PHP
$this->addError('email', 'That domain is blocklisted.');
$this->resetValidation('email');
$this->resetValidation();               // clear all
$this->validateOnly('email');

// Throwing exactly as you would in a controller
throw ValidationException::withMessages([
    'code' => 'Wrong confirmation code.',
]);

Form objects

Once a form grows, move it into its own class. The component stays thin, and the rules and data can be reused between create and edit.

Bash
php artisan livewire:form PostForm
app/Livewire/Forms/PostForm.php
<?php

namespace App\Livewire\Forms;

use App\Models\Post;
use Livewire\Attributes\Validate;
use Livewire\Form;

class PostForm extends Form
{
    public ?Post $post = null;

    #[Validate('required|string|min:5|max:180')]
    public string $title = '';

    #[Validate('required|string|min:50')]
    public string $body = '';

    #[Validate('nullable|date|after_or_equal:today')]
    public ?string $publishAt = null;

    public function setPost(Post $post): void
    {
        $this->post      = $post;
        $this->title     = $post->title;
        $this->body      = $post->body;
        $this->publishAt = $post->publish_at?->toDateString();
    }

    public function store(): Post
    {
        $this->validate();

        return Post::create($this->except('post'));
    }

    public function update(): void
    {
        $this->validate();

        $this->post->update($this->except('post'));
    }
}
app/Livewire/PostEditor.php
class PostEditor extends Component
{
    public PostForm $form;

    public function mount(?Post $post = null): void
    {
        if ($post?->exists) {
            $this->form->setPost($post);
        }
    }

    public function save(): void
    {
        $this->form->post
            ? $this->form->update()
            : $this->form->store();

        $this->redirect(route('posts.index'), navigate: true);
    }
}
Blade
<form wire:submit="save">
    <input wire:model="form.title">
    @error('form.title') <span>{{ $message }}</span> @enderror

    <textarea wire:model="form.body"></textarea>
    @error('form.body') <span>{{ $message }}</span> @enderror

    <button type="submit">Save</button>
</form>

10. Nested components and wire:key

Components nest. Each child is an independent unit with its own state and its own update cycle: a click inside a child does not re-render the parent.

Blade
<div>
    <h1>Orders</h1>

    @foreach ($orders as $order)
        <livewire:order-row :order="$order" :key="'order-'.$order->id" />
    @endforeach
</div>
:key is mandatory in loops. Without a unique key the morph algorithm confuses elements when sorting, filtering or deleting: field values "move" into neighbouring rows. The key must be stable — $loop->index will not do, use the record id.

Passing data down

Parameters are handed over once, in mount(). On later parent renders the child does not update automatically — it lives its own life.

PHP
use Livewire\Attributes\Reactive;

class OrderTotal extends Component
{
    // With #[Reactive] the value arrives from the parent on each of its renders.
    #[Reactive]
    public int $quantity;
}

Calling the parent

Blade
<button wire:click="$parent.refreshList()">Refresh the list</button>

Two-way binding: #[Modelable]

Lets you put wire:model on the component itself — handy for input widgets.

app/Livewire/RatingInput.php
use Livewire\Attributes\Modelable;

class RatingInput extends Component
{
    #[Modelable]
    public int $value = 0;

    public function set(int $value): void
    {
        $this->value = $value;
    }
}
Blade
{{-- In the parent view --}}
<livewire:rating-input wire:model.live="review.rating" />

Rendering children conditionally

Blade
@if ($showDetails)
    <livewire:order-details :order-id="$orderId" :key="'details-'.$orderId" />
@endif
When to split. Extract a child component when the block has its own state or updates independently (a table row, a modal, a widget). If the block is just markup, use a plain Blade partial — it is cheaper.

11. Events

Events connect components that are not in a parent-child relationship. In Livewire 3 you send with dispatch() (version 2 used emit()).

Dispatching

PHP
// A plain event
$this->dispatch('post-created');

// With named parameters
$this->dispatch('post-created', postId: $post->id, title: $post->title);

// To one specific component
$this->dispatch('refresh')->to(OrderList::class);

// To itself only
$this->dispatch('recalculate')->self();
Blade
{{-- Straight from the view --}}
<button wire:click="$dispatch('open-modal', { name: 'create-order' })">New order</button>

Listening

PHP
use Livewire\Attributes\On;

class OrderList extends Component
{
    public array $orders = [];

    #[On('post-created')]
    public function onPostCreated(int $postId, string $title): void
    {
        $this->orders[] = ['id' => $postId, 'title' => $title];
    }

    // Dynamic event name
    #[On('order-updated.{orderId}')]
    public function onOrderUpdated(): void
    {
        $this->refreshList();
    }
}

The alternative is the $listeners array (version 2 compatible):

PHP
protected $listeners = [
    'post-created' => 'onPostCreated',
    'refresh'      => '$refresh',
];

Events to the browser

Livewire can dispatch an ordinary DOM event, caught by Alpine.js or your own JS. That is the right way to show toasts and open modals.

PHP
$this->dispatch('notify', type: 'success', message: 'Order saved');
Blade
<div x-data="{ show: false, message: '' }"
     x-on:notify.window="message = $event.detail.message; show = true; setTimeout(() => show = false, 3000)"
     x-show="show"
     x-transition
     class="toast">
    <span x-text="message"></span>
</div>
JavaScript
document.addEventListener('notify', (event) => {
    console.log(event.detail.message);
});
Do not overuse events. Every event costs an extra request cycle for each listening component. If two blocks always change together, merging them into one component is usually cheaper.

12. Computed properties

#[Computed] gives you derived data without storing it in state. The result is cached for the duration of one request, so using it three times in a view does not cost three queries.

PHP
use Livewire\Attributes\Computed;
use Illuminate\Support\Collection;

class Cart extends Component
{
    public array $items = [];

    #[Computed]
    public function products(): Collection
    {
        return Product::whereIn('id', array_keys($this->items))->get();
    }

    #[Computed]
    public function subtotal(): float
    {
        return $this->products->sum(
            fn (Product $product) => $product->price * $this->items[$product->id]
        );
    }

    #[Computed]
    public function total(): float
    {
        return round($this->subtotal * 1.2, 2);   // with VAT
    }
}
Blade
<div>
    @foreach ($this->products as $product)
        <div>{{ $product->name }} — {{ $this->items[$product->id] }} pcs</div>
    @endforeach

    <p>Subtotal: {{ number_format($this->subtotal, 2) }} €</p>
    <p>Total incl. VAT: {{ number_format($this->total, 2) }} €</p>
</div>
Access through $this. In the view a computed property is $this->products, not $products. That is what sets it apart from ordinary public properties.

Caching across requests

PHP
// Cached for 5 minutes in the shared application cache
#[Computed(persist: true, seconds: 300)]
public function statistics(): array
{
    return [
        'orders'  => Order::whereMonth('created_at', now()->month)->count(),
        'revenue' => Order::whereMonth('created_at', now()->month)->sum('total'),
    ];
}

// One cache entry shared by all users
#[Computed(cache: true, key: 'global-stats')]
public function globalStats(): array
{
    return app(StatsService::class)->build();
}

Busting the cache

PHP
public function addItem(int $productId): void
{
    $this->items[$productId] = ($this->items[$productId] ?? 0) + 1;

    unset($this->products, $this->subtotal, $this->total);   // drop the cache
}

render() data vs #[Computed]

Neither stores data in state. The difference is reach: render() hands variables to the view only, while #[Computed] is available in PHP methods and in the view, and is cached.

PHP
public function render()
{
    return view('livewire.orders', [
        'orders' => Order::query()
            ->when($this->search, fn ($q) => $q->where('number', 'like', "%{$this->search}%"))
            ->latest()
            ->paginate(20),
    ]);
}

13. State in the URL and the session

Filters, search and the active tab should survive a reload and be shareable as a link. The #[Url] attribute keeps a property in sync with the query string.

PHP
use Livewire\Attributes\Url;

class ProductCatalog extends Component
{
    #[Url]
    public string $search = '';

    // A different parameter name in the URL
    #[Url(as: 'cat')]
    public ?int $categoryId = null;

    // Keep it out of the URL while it equals the initial value
    #[Url(except: '')]
    public string $sort = 'popular';

    // Preserve across wire:navigate visits
    #[Url(keep: true)]
    public int $perPage = 24;

    // Use history.pushState instead of replaceState
    #[Url(history: true)]
    public string $tab = 'all';
}

The result: /catalog?search=laptop&cat=5&sort=price-asc&tab=sale

Storing in the session

PHP
use Livewire\Attributes\Session;

class Sidebar extends Component
{
    #[Session]
    public bool $collapsed = false;

    #[Session(key: 'admin.table.density')]
    public string $density = 'comfortable';
}

Session and flash by hand

PHP
public function save(): void
{
    $this->validate();

    Setting::updateOrCreate(['key' => 'theme'], ['value' => $this->theme]);

    session()->flash('status', 'Settings saved');
}
Blade
@if (session('status'))
    <div class="docs-note docs-note--tip">{{ session('status') }}</div>
@endif
Do not push everything into the URL. Every #[Url] property hits the History API when it changes. For fields that change on every keystroke, combine it with wire:model.live.debounce.

14. File uploads

The WithFileUploads trait adds real uploads: the file goes to the server as soon as it is picked, lands in temporary storage and becomes available as a TemporaryUploadedFile object.

PHP
use Livewire\WithFileUploads;
use Livewire\Attributes\Validate;
use Livewire\Features\SupportFileUploads\TemporaryUploadedFile;

class AvatarUploader extends Component
{
    use WithFileUploads;

    #[Validate('required|image|mimes:jpg,jpeg,png,webp|max:4096')]   // up to 4 MB
    public $avatar;

    #[Validate(['documents.*' => 'file|mimes:pdf,docx|max:10240'])]
    public array $documents = [];

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

        $path = $this->avatar->store('avatars', 'public');

        auth()->user()->update(['avatar_path' => $path]);

        $this->reset('avatar');

        $this->dispatch('notify', message: 'Avatar updated');
    }

    public function removeDocument(int $index): void
    {
        unset($this->documents[$index]);

        $this->documents = array_values($this->documents);
    }
}
Blade
<form wire:submit="save">
    <input type="file" wire:model="avatar" accept="image/*">

    {{-- Upload progress --}}
    <div wire:loading wire:target="avatar" class="text-sm">Uploading…</div>

    {{-- Preview before saving --}}
    @if ($avatar)
        <img src="{{ $avatar->temporaryUrl() }}" alt="Preview" class="w-32 h-32 object-cover rounded">
    @endif

    @error('avatar') <p class="text-red-600">{{ $message }}</p> @enderror

    <button type="submit" wire:loading.attr="disabled">Save</button>
</form>

A precise progress bar

Blade
<div x-data="{ progress: 0, uploading: false }"
     x-on:livewire-upload-start="uploading = true"
     x-on:livewire-upload-finish="uploading = false; progress = 0"
     x-on:livewire-upload-error="uploading = false"
     x-on:livewire-upload-progress="progress = $event.detail.progress">

    <input type="file" wire:model="avatar">

    <div x-show="uploading" class="h-2 bg-gray-200 rounded">
        <div class="h-2 bg-green-600 rounded" :style="`width: ${progress}%`"></div>
    </div>
</div>

Configuring temporary storage

config/livewire.php
'temporary_file_upload' => [
    'disk'       => 's3',            // or null for the default disk
    'rules'      => ['file', 'max:12288'],
    'directory'  => 'livewire-tmp',
    'middleware' => 'throttle:60,1',
    'preview_mimes' => ['png', 'jpeg', 'jpg', 'webp', 'gif', 'mp4', 'pdf'],
    'max_upload_time' => 5,          // minutes before automatic cleanup
],
Check the PHP limits. upload_max_filesize, post_max_size and max_execution_time in php.ini, plus client_max_body_size in nginx, must all be at least as large as your max: rule. Otherwise the upload dies without a readable error.

15. Pagination

The WithPagination trait wires up Laravel pagination without page reloads.

PHP
use Livewire\WithPagination;
use Livewire\Attributes\Url;

class OrderTable extends Component
{
    use WithPagination;

    #[Url]
    public string $search = '';

    #[Url]
    public string $status = '';

    public string $sortField = 'created_at';
    public string $sortDirection = 'desc';
    public int $perPage = 25;

    // Back to page one whenever a filter changes.
    public function updatedSearch(): void
    {
        $this->resetPage();
    }

    public function updatedStatus(): void
    {
        $this->resetPage();
    }

    public function sortBy(string $field): void
    {
        if ($this->sortField === $field) {
            $this->sortDirection = $this->sortDirection === 'asc' ? 'desc' : 'asc';
        } else {
            $this->sortField = $field;
            $this->sortDirection = 'asc';
        }

        $this->resetPage();
    }

    public function render()
    {
        return view('livewire.order-table', [
            'orders' => Order::query()
                ->with('customer')
                ->when($this->search, fn ($q) => $q->where('number', 'like', "%{$this->search}%"))
                ->when($this->status, fn ($q) => $q->where('status', $this->status))
                ->orderBy($this->sortField, $this->sortDirection)
                ->paginate($this->perPage),
        ]);
    }
}
Blade
<div>
    <input type="search" wire:model.live.debounce.400ms="search" placeholder="Search by number">

    <table>
        <thead>
            <tr>
                <th wire:click="sortBy('number')" style="cursor:pointer">Number</th>
                <th wire:click="sortBy('created_at')" style="cursor:pointer">Date</th>
                <th wire:click="sortBy('total')" style="cursor:pointer">Total</th>
            </tr>
        </thead>
        <tbody>
            @forelse ($orders as $order)
                <tr wire:key="order-{{ $order->id }}">
                    <td>{{ $order->number }}</td>
                    <td>{{ $order->created_at->format('d/m/Y') }}</td>
                    <td>{{ number_format($order->total, 2) }}</td>
                </tr>
            @empty
                <tr><td colspan="3">No orders found</td></tr>
            @endforelse
        </tbody>
    </table>

    {{ $orders->links() }}
</div>

Useful details

PHP
// A custom page parameter name — needed when a page has two paginators
protected string $paginationTheme = 'tailwind';   // or 'bootstrap'

public function render()
{
    return view('livewire.dashboard', [
        'orders'   => Order::paginate(10, pageName: 'orders-page'),
        'invoices' => Invoice::paginate(10, pageName: 'invoices-page'),
    ]);
}
Performance. On large tables paginate() runs an extra COUNT(*). If you do not need the total page count, use simplePaginate() — it is noticeably cheaper.

16. Loading states

Every action is a network request. Without feedback the interface feels frozen. Livewire ships declarative directives that need no JavaScript at all.

Blade
{{-- Show during any request from this component --}}
<div wire:loading>Loading…</div>

{{-- Hide during a request --}}
<div wire:loading.remove>Content</div>

{{-- Only for one action --}}
<button wire:click="save">Save</button>
<span wire:loading wire:target="save">Saving…</span>

{{-- For several targets --}}
<span wire:loading wire:target="save,delete,publish">Working…</span>

{{-- For a specific property --}}
<span wire:loading wire:target="search">Searching…</span>

{{-- Exclude a target --}}
<div wire:loading wire:target.except="search">Updating…</div>

Modifiers

Blade
{{-- Disable the button --}}
<button wire:click="save" wire:loading.attr="disabled">Save</button>

{{-- Add a CSS class --}}
<button wire:click="save" wire:loading.class="opacity-50 cursor-wait">Save</button>

{{-- Remove a class --}}
<div wire:loading.class.remove="bg-white">…</div>

{{-- Delay: only show the indicator if the request takes over 300 ms --}}
<div wire:loading.delay>Loading…</div>

{{-- Exact thresholds: shortest 50ms, shorter 100ms, short 150ms,
     default 200ms, long 300ms, longer 500ms, longest 1s --}}
<div wire:loading.delay.long>Loading…</div>

{{-- Control the display mode --}}
<div wire:loading.flex>…</div>
<div wire:loading.grid>…</div>
<div wire:loading.inline-flex>…</div>

Unsaved changes: wire:dirty

Blade
<input wire:model="title">

<span wire:dirty wire:target="title" class="text-amber-600">You have unsaved changes</span>

<button wire:click="save" wire:dirty.class="ring-2 ring-amber-400">Save</button>

Lost connection: wire:offline

Blade
<div wire:offline class="banner banner--warn">
    No connection to the server. Changes are not being saved.
</div>

A skeleton on first load

Blade
<div wire:init="loadHeavyData">
    @if ($loaded)
        {{-- the real data --}}
    @else
        <div class="skeleton h-40 w-full animate-pulse bg-gray-200"></div>
    @endif
</div>

18. Lazy loading and polling

Lazy-loading a component

A heavy component does not have to render in the first response: the page ships immediately with a placeholder and the body arrives in a second request.

Blade
<livewire:revenue-chart lazy />
PHP
use Livewire\Attributes\Lazy;

#[Lazy]
class RevenueChart extends Component
{
    public function placeholder(): string
    {
        return <<<'BLADE'
            <div class="skeleton h-64 w-full animate-pulse rounded bg-gray-200"></div>
        BLADE;
    }

    public function render()
    {
        return view('livewire.revenue-chart', [
            'points' => app(RevenueService::class)->monthly(),   // an expensive query
        ]);
    }
}

By default a lazy component loads right after the page paints. #[Lazy(isolate: false)] bundles the requests of several lazy components into one, and lazy="on-load" / lazy="on-scroll" control when loading happens.

Blade
{{-- Load when the block enters the viewport --}}
<livewire:revenue-chart lazy="on-scroll" />

Deferred initialisation: wire:init

Blade
<div wire:init="loadStats">
    @if ($stats)
        …
    @else
        <div class="skeleton"></div>
    @endif
</div>

Polling the server: wire:poll

Blade
{{-- Refresh every 2 s (the default is 2500 ms) --}}
<div wire:poll>…</div>

{{-- A custom interval --}}
<div wire:poll.5s>…</div>
<div wire:poll.750ms>…</div>

{{-- Call a specific method --}}
<div wire:poll.10s="refreshQueue">…</div>

{{-- Stop polling while the tab is hidden --}}
<div wire:poll.visible.5s="refreshQueue">…</div>

{{-- Stop after 5 minutes of user inactivity --}}
<div wire:poll.keep-alive.5s>…</div>
What polling costs. wire:poll.2s on a page open at 100 desks is 3,000 requests per minute hitting your PHP processes. Always add .visible, pick a sane interval, and consider websockets (Laravel Echo + Reverb) for genuinely live data.

19. Alpine.js, $wire and JS hooks

Alpine.js ships with Livewire 3. Inside a component you get the $wire object — a proxy onto the PHP component's state and methods, straight from JavaScript.

Blade
<div x-data="{ open: false }">
    {{-- Purely client-side state: the server is not involved --}}
    <button x-on:click="open = !open">Details</button>

    <div x-show="open" x-transition>
        {{-- Read a property --}}
        <p x-text="$wire.title"></p>

        {{-- Write a property --}}
        <button x-on:click="$wire.title = 'New title'">Rename</button>

        {{-- Call a method (returns a Promise) --}}
        <button x-on:click="$wire.save()">Save</button>

        {{-- Await the result --}}
        <button x-on:click="await $wire.calculate(); open = false">Calculate</button>

        {{-- Call without re-rendering --}}
        <button x-on:click="$wire.$set('tab', 'stats', false)">Stats</button>
    </div>
</div>

Two-way binding: $wire.entangle

Blade
<div x-data="{ query: $wire.entangle('search') }">
    <input x-model="query">
    <p x-show="query.length > 0">Searching for: <span x-text="query"></span></p>
</div>

{{-- Deferred sync: do not fire a request per character --}}
<div x-data="{ query: $wire.entangle('search').live }">…</div>

Your own scripts inside a component

Blade
@script
<script>
    // Runs once when the component initialises.
    const chart = new Chart(document.getElementById('sales'), {
        type: 'line',
        data: @json($chartData),
    });

    // $wire is available here too.
    $wire.on('data-updated', ({ points }) => {
        chart.data.datasets[0].data = points;
        chart.update();
    });
</script>
@endscript
Blade
{{-- Load an external library once for the whole page --}}
@assets
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
@endassets

Ignoring a subtree

When a third-party widget manages the DOM itself, the morph algorithm only gets in the way.

Blade
<div wire:ignore>
    <select id="select2-field">…</select>
</div>

{{-- Ignore children but keep updating the element's own attributes --}}
<div wire:ignore.self>…</div>

Global JS hooks

JavaScript
document.addEventListener('livewire:init', () => {

    // Before every outgoing request
    Livewire.hook('request', ({ uri, options, payload, respond, succeed, fail }) => {
        options.headers['X-Tenant'] = window.tenantId;

        succeed(({ status, json }) => {
            console.debug('Livewire responded', status);
        });

        fail(({ status, preventDefault }) => {
            if (status === 419) {
                preventDefault();
                window.location.reload();   // the CSRF token expired
            }
        });
    });

    // Before and after morphing an individual element
    Livewire.hook('morph.updated', ({ el, component }) => {});

    // A component has initialised
    Livewire.hook('component.init', ({ component }) => {});
});

// Programmatic access to components
Livewire.dispatch('refresh-orders');
Livewire.find('component-id').call('save');
Livewire.all().forEach((component) => component.$refresh());

20. Testing

Livewire ships its own testing API on top of Laravel's. The tests are fast — no browser needed, plain PHPUnit or Pest will do.

tests/Feature/CounterTest.php
<?php

use App\Livewire\Counter;
use Livewire\Livewire;

it('increments the counter', function () {
    Livewire::test(Counter::class)
        ->assertSet('count', 0)
        ->call('increment')
        ->assertSet('count', 1)
        ->call('increment')
        ->assertSet('count', 2)
        ->call('decrement')
        ->assertSet('count', 1);
});
tests/Feature/ContactFormTest.php
use App\Livewire\ContactForm;
use App\Models\Contact;
use Livewire\Livewire;

it('validates the required fields', function () {
    Livewire::test(ContactForm::class)
        ->set('name', '')
        ->set('email', 'not-an-email')
        ->call('submit')
        ->assertHasErrors([
            'name'  => 'required',
            'email' => 'email',
        ])
        ->assertNoRedirect();
});

it('stores a valid enquiry', function () {
    Livewire::test(ContactForm::class)
        ->set('name', 'Irene Kovac')
        ->set('email', 'irene@example.com')
        ->set('message', str_repeat('We need a website for a clinic. ', 2))
        ->set('consent', true)
        ->call('submit')
        ->assertHasNoErrors()
        ->assertDispatched('notify');

    expect(Contact::where('email', 'irene@example.com')->exists())->toBeTrue();
});

The main assertions

MethodChecks
assertSet('prop', $value)A property's value
assertNotSet('prop', $value)The value differs
assertSee('text')The text is in the markup
assertDontSee('text')The text is absent
assertSeeHtml('<b>')The markup is present
assertHasErrors(['email'])Validation errors
assertHasNoErrors()No errors
assertDispatched('event')An event was dispatched
assertRedirect(route(…))A redirect happened
assertStatus(403)HTTP status
assertForbidden()Access denied
assertCount('items', 3)Array or collection size

Authentication, parameters, events

PHP
// As a signed-in user
Livewire::actingAs($admin)
    ->test(OrderTable::class)
    ->assertSee('All orders');

// With mount() parameters
Livewire::test(OrderCard::class, ['order' => $order, 'mode' => 'full'])
    ->assertSee($order->number);

// Receiving an event
Livewire::test(OrderList::class)
    ->dispatch('order-created', orderId: 42)
    ->assertSee('Order #42');

// File upload
use Illuminate\Http\UploadedFile;

Livewire::test(AvatarUploader::class)
    ->set('avatar', UploadedFile::fake()->image('avatar.jpg', 400, 400))
    ->call('save')
    ->assertHasNoErrors();

// A component inside a page
$this->get('/dashboard')
    ->assertSeeLivewire(Dashboard::class)
    ->assertDontSeeLivewire(AdminPanel::class);
What to test first. Authorisation inside actions, validation rules and state transitions. That is where functionality breaks and where the security holes hide.

21. Security

The core principle. Every public component method is an open HTTP endpoint. Every public property can be changed by the client. Treat a component like a controller, not like an internal class.

1. Authorise inside every action

PHP
public function delete(int $postId): void
{
    $post = Post::findOrFail($postId);

    $this->authorize('delete', $post);   // policy — non-negotiable

    $post->delete();
}
PHP
// Authorising the whole component
public function mount(Project $project): void
{
    $this->authorize('view', $project);

    $this->project = $project;
}

2. #[Locked] on identifiers

PHP
use Livewire\Attributes\Locked;

class InvoiceEditor extends Component
{
    #[Locked]
    public int $invoiceId;      // cannot be tampered with from the frontend

    public string $note = '';
}

Without #[Locked] one line in the browser console is enough:

JavaScript (attack)
Livewire.find('...').set('invoiceId', 999)   // someone else's invoice

3. Rules for model binding

wire:model="post.title" only works when the field is allowed by a validation rule — that is how Livewire guards against mass assignment.

PHP
protected function rules(): array
{
    return [
        'post.title' => 'required|string|max:180',
        'post.body'  => 'required|string',
        // 'post.user_id' deliberately omitted — otherwise the post could be reassigned
    ];
}

4. Escape your output

Blade
{{-- Safe: escaped --}}
{{ $comment->body }}

{{-- Dangerous: raw markup from a user --}}
{!! $comment->body !!}

{{-- If you need markup, sanitise it server-side --}}
{!! clean($comment->body) !!}

5. Rate limiting

PHP
use Illuminate\Support\Facades\RateLimiter;

public function login(): void
{
    $key = 'login:' . request()->ip();

    if (RateLimiter::tooManyAttempts($key, 5)) {
        throw ValidationException::withMessages([
            'email' => 'Too many attempts. Try again in a minute.',
        ]);
    }

    RateLimiter::hit($key, 60);

    // …
}

6. Never keep secrets in properties

PHP
class PaymentForm extends Component
{
    public string $cardLast4 = '';        // fine

    // DO NOT: this reaches the browser in clear text inside wire:snapshot
    // public string $apiSecret = '';
    // public string $fullCardNumber = '';

    protected function gateway(): PaymentGateway
    {
        return app(PaymentGateway::class);   // secrets stay on the server
    }
}

Pre-release checklist

  • $this->authorize() appears in every action that mutates data.
  • All identifiers are marked #[Locked].
  • Validation rules exclude internal fields (user_id, role, price).
  • Public properties hold no tokens, keys or unnecessary personal data.
  • File uploads are constrained by MIME type and size.
  • Login and contact forms are rate limited.
  • {!! !!} is used only for trusted or sanitised markup.

22. Performance

1. Keep public state small

Bad
public Collection $products;   // 500 models travel to the client and back

public function mount(): void
{
    $this->products = Product::with('category', 'images')->get();
}
Good
public function render()
{
    return view('livewire.catalog', [
        'products' => Product::with('category')->paginate(24),
    ]);
}

2. Do not fire a request per keystroke

Blade
{{-- Bad: a request per character --}}
<input wire:model.live="search">

{{-- Good --}}
<input wire:model.live.debounce.400ms="search">

3. Split heavy pages into components

Updating a child does not re-render the parent. A dashboard built from six independent widgets feels markedly snappier than one monolith.

4. Cache expensive work

PHP
#[Computed(persist: true, seconds: 600)]
public function monthlyRevenue(): array
{
    return app(RevenueService::class)->byMonth();
}

5. Watch for N+1

PHP
// Bad — one query per row in the view
$orders = Order::paginate(50);

// Good
$orders = Order::with(['customer', 'items.product'])->paginate(50);

Turn on strict mode in development so N+1 fails loudly:

app/Providers/AppServiceProvider.php
public function boot(): void
{
    Model::preventLazyLoading(! app()->isProduction());
}

6. wire:key in lists

Without keys the morph algorithm rebuilds more nodes than needed — and gets it wrong.

7. Lazy-load heavy blocks

Blade
<livewire:revenue-chart lazy="on-scroll" />

8. Go easy on polling

wire:poll.visible.10s instead of wire:poll saves the server an order of magnitude in requests.

SymptomLikely cause
300–800 ms lag on every actionExpensive render() or N+1
Enormous page markupModel collections in public properties
Rising PHP-FPM loadAggressive wire:poll or .live without debounce
Fields "jump" when the list updateswire:key is missing
The whole page re-rendersEverything in one component, no split

23. Common errors and fixes

"Component must have a single root element"

The view has several root nodes, or text or a comment at the top level. Wrap everything in one <div>.

Bad
<h1>Heading</h1>
<p>Text</p>
Good
<div>
    <h1>Heading</h1>
    <p>Text</p>
</div>

"Livewire encountered corrupt data"

The snapshot signature does not match. Causes: a changed APP_KEY, a page served from cache after a deploy, two tabs with different sessions. A reload usually fixes it; after a deploy run php artisan optimize:clear.

Clicks do nothing and the console is empty

  • @livewireScripts is missing from the layout.
  • A second Alpine.js instance is loaded (Livewire 3 already bundles it).
  • A JS error higher up the page aborted execution.
  • The element sits inside wire:ignore.

Field values "move" between rows

The loop is missing wire:key or :key. The key must be stable and unique.

Blade
@foreach ($rows as $row)
    <div wire:key="row-{{ $row->id }}">…</div>
@endforeach

"Unable to set component data. Public property not found"

You bound wire:model to a property that does not exist or is not public. Check the spelling and the visibility modifier.

"Cannot bind to model data without validation rules"

A binding like wire:model="post.title" needs a rule for post.title in rules() or #[Validate].

A third-party widget breaks after an update

Select2, Flatpickr, TinyMCE and friends mutate the DOM themselves. Wrap them in wire:ignore and synchronise manually.

Blade
<div wire:ignore x-data x-init="
    const picker = flatpickr($refs.input, {
        onChange: (dates, str) => $wire.set('date', str),
    });
">
    <input x-ref="input" type="text">
</div>

File uploads fail silently

Check upload_max_filesize and post_max_size in PHP, plus client_max_body_size in nginx. They must exceed the limit in your max: rule.

The modal does not close after saving

Dispatch a browser event and handle it in Alpine instead of relying on a re-render.

PHP
$this->dispatch('close-modal', name: 'order-form');

Scripts stop working after wire:navigate

Move initialisation from DOMContentLoaded to livewire:navigated.

Error 419 (Page Expired)

The session expired. Raise SESSION_LIFETIME, or catch the status in the request hook and reload the page — see the example in chapter 19.

24. Cheat sheet

Blade directives

DirectivePurpose
wire:modelBind a field to a property (deferred)
wire:model.liveBind with a request on every change
wire:model.blurSend on focus loss
wire:clickCall a method on click
wire:submitHandle a form submit
wire:keydown.enterReact to a key
wire:changeReact to change
wire:confirmConfirm before acting
wire:loadingLoading indicator
wire:targetScope the indicator to an action
wire:dirtyThere are unsaved changes
wire:offlineNo connection
wire:pollPeriodic refresh
wire:initCall a method right after render
wire:navigateSPA-style link navigation
wire:keyElement identity inside a loop
wire:ignoreExclude a subtree from morphing
wire:transitionEnter/leave animation
wire:streamStream content

PHP attributes

AttributePurpose
#[Validate]Validation rule for a property
#[Locked]Forbid changes from the frontend
#[Computed]Computed property with caching
#[Url]Sync with the query string
#[Session]Persist the value in the session
#[On]Event listener
#[Reactive]Refresh a prop from the parent
#[Modelable]Support wire:model on the component
#[Lazy]Lazy-load the component
#[Layout]Layout for a full-page component
#[Title]Page title
#[Renderless]Method that skips re-rendering

Handy component methods

PHP
$this->reset();                       // all properties back to their initial values
$this->reset('search', 'page');       // reset a selection
$this->only('title', 'body');         // array of some properties
$this->except('password');            // everything but the named ones
$this->fill(['title' => 'New']);      // mass assignment
$this->pull('draft');                 // read and reset

$this->validate();
$this->validateOnly('email');
$this->resetValidation();
$this->addError('email', 'Message');

$this->dispatch('saved', id: $post->id);
$this->redirect('/orders', navigate: true);
$this->redirectRoute('orders.index', navigate: true);

$this->skipRender();                  // skip re-rendering this cycle
$this->js('alert("Done")');           // run JS on the client
$this->stream(to: 'answer', content: $chunk);

Artisan commands

Bash
php artisan livewire:make Orders/OrderTable
php artisan livewire:make Counter --inline
php artisan livewire:form PostForm
php artisan livewire:attribute ValidPhone
php artisan livewire:publish --config
php artisan livewire:publish --assets

Official resources