Livewire 3 — kompletný sprievodca
Livewire umožňuje stavať dynamické rozhrania v Laraveli pomocou PHP a Blade — bez samostatnej frontend aplikácie a bez API vrstvy. Táto príručka vedie od inštalácie po produkciu: komponenty, stav, formuláre, udalosti, nahrávanie súborov, testovanie, bezpečnosť a výkon.
1. Čo je Livewire
Livewire je plnohodnotný framework pre Laravel, ktorý umožňuje stavať dynamické rozhrania v PHP a Blade bez toho, aby ste opustili backend. Nepíšete samostatnú SPA, nestaviate REST/GraphQL vrstvu a neudržiavate druhý dátový model v JavaScripte.
Ako to funguje
Livewire komponent je PHP trieda a prislúchajúca Blade šablóna. Pri prvom načítaní stránky sa komponent vykreslí na serveri ako bežné HTML. Ďalej malý JS runtime zachytáva interakcie používateľa (klik, písanie, submit), posiela AJAX požiadavku na server s aktuálnym stavom komponentu, server komponent znova vykreslí a vráti nové HTML. Runtime porovná staré a nové DOM stromy a bodovo opraví rozdiely — stránka sa nenačítava odznova.
Prehliadač Server
│ │
│ klik na wire:click="save" │
├───────── POST /livewire/update ┤
│ { snapshot, calls, updates } │
│ ├── obnoviť komponent zo snapshotu
│ ├── aplikovať zmeny vlastností
│ ├── zavolať metódu save()
│ ├── spustiť render()
│ { snapshot, html, effects } │
├◄───────────────────────────────┤
├── morph DOM (len rozdiely) │
▼ ▼Kedy je Livewire správna voľba
- Administrácie, CRM, dashboardy, interné panely — veľa formulárov a tabuliek, málo zložitej animácie.
- Formuláre so závislými poľami, viackrokoví sprievodcovia, živá validácia.
- Tabuľky s vyhľadávaním, filtrami, radením a stránkovaním.
- Tím je silný v PHP a nechce udržiavať samostatný frontend stack.
- Potrebujete rýchlo dodávať funkcionalitu bez duplikovania logiky medzi PHP a JS.
Kedy siahnuť po niečom inom
- Rozhrania s vysokou frekvenciou zmien: online editory, plátno, drag-and-drop nástroje, hry.
- Offline-first aplikácie a PWA so zložitou lokálnou synchronizáciou.
- Mobilné aplikácie — tam potrebujete API, nie HTML-over-the-wire.
- Slabé alebo pomalé pripojenie: každý cyklus je sieťový round-trip.
Verzie
Táto príručka popisuje Livewire 3.x. Požiadavky: PHP 8.1+, Laravel 10 a vyššie.
V tretej verzii je Alpine.js súčasťou balíka, zmenili sa názvy udalostí (dispatch namiesto
emit), pribudli PHP atribúty (#[Computed], #[Validate],
#[Url]) a wire:model je predvolene odložený.
2. Inštalácia a nastavenie
Inštalácia do existujúceho Laravel projektu je jeden príkaz.
composer require livewire/livewire
V Livewire 3 nemusíte assety pripájať zvlášť: balík sám vkladá @livewireStyles
a @livewireScripts do layoutu. Ak však používate neštandardný layout alebo CSP,
direktívy môžete uviesť ručne.
<!DOCTYPE html>
<html lang="sk">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{ $title ?? 'Aplikácia' }}</title>
<link rel="stylesheet" href="{{ asset('css/app.css') }}">
@livewireStyles
</head>
<body class="antialiased">
{{ $slot }}
@livewireScripts
</body>
</html>@livewireScripts v layoute alebo je uvedený pred vaším vlastným Alpine.js. Livewire 3 už
Alpine obsahuje — druhá inštancia Alpine rozbije reaktivitu.
Publikovanie konfigurácie
php artisan livewire:publish --configHlavné parametre v config/livewire.php:
| Parameter | Účel |
|---|---|
class_namespace | Namespace tried komponentov. Predvolene App\Livewire. |
view_path | Priečinok Blade šablón komponentov. |
layout | Layout pre full-page komponenty. |
temporary_file_upload | Disk, životnosť a pravidlá pre dočasné nahrávanie. |
inject_assets | Automatické vkladanie CSS/JS. Vypnite, ak direktívy uvádzate ručne. |
navigate.show_progress_bar | Indikátor priebehu pre wire:navigate. |
Overenie inštalácie
php artisan livewire:make Counter
# CLASS: app/Livewire/Counter.php
# VIEW: resources/views/livewire/counter.blade.php3. Prvý komponent
Komponent tvoria dva súbory: trieda a šablóna.
<?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');
}
}<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">Vynulovať</button>
</div><div>.
Tri spôsoby vykreslenia komponentu
{{-- 1. Tag syntax (odporúčané) --}}
<livewire:counter />
{{-- 2. Direktíva --}}
@livewire('counter')
{{-- 3. S parametrami --}}
<livewire:counter :start="10" title="Počítadlo objednávok" />Full-page komponenty
Komponent môžete pripojiť priamo na route — vtedy sa vykreslí ako samostatná stránka.
use App\Livewire\Dashboard;
Route::get('/dashboard', Dashboard::class)->middleware('auth')->name('dashboard');use Livewire\Attributes\Layout;
use Livewire\Attributes\Title;
class Dashboard extends Component
{
#[Layout('layouts.app')]
#[Title('Ovládací panel')]
public function render()
{
return view('livewire.dashboard');
}
}Inline komponenty
Pri veľmi malých komponentoch môžete šablónu vrátiť ako reťazec — samostatný Blade súbor netreba.
public function render()
{
return <<<'BLADE'
<div>
<button wire:click="$refresh">Obnoviť</button>
</div>
BLADE;
}4. Vlastnosti komponentu
Verejné vlastnosti triedy sú automaticky dostupné v Blade šablóne a zachovávajú sa medzi požiadavkami. Práve ony tvoria stav komponentu.
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');
}
}Aké typy možno ukladať
Medzi požiadavkami sa stav serializuje do JSON, preto je množina povolených typov obmedzená.
| Možno | Nemožno |
|---|---|
string, int, float, bool, null |
Uzávery (Closure) |
array so skalárnymi hodnotami |
Zdroje (resource), streamy |
Eloquent modely a Collection modelov |
Ľubovoľné objekty bez podpory Wireable/Synth |
Carbon, DateTime, Stringable, enum |
Objekty s odkazmi na spojenia, PDO a podobne |
Inicializácia: mount()
Metóda mount() je konštruktor komponentu. Spustí sa raz, pri prvom vykreslení, a dostane
parametre odovzdané z Blade.
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;
}
}<livewire:order-card :order="$order" mode="full" :key="$order->id" />Ochrana vlastností: #[Locked]
Predvolene môže klient zmeniť hodnotu ktorejkoľvek verejnej vlastnosti. Pri identifikátoroch a všetkom,
čo ovplyvňuje prístupové práva, je to bezpečnostná diera. Atribút #[Locked] zmenu
z frontendu zakáže.
use Livewire\Attributes\Locked;
class InvoiceEditor extends Component
{
#[Locked]
public int $invoiceId; // klient nepodstrčí cudzie id
public string $comment = '';
}Vlastnosti skryté pred JSON
Vlastnosti s modifikátormi protected a private sa neserializujú a
medzi požiadavkami sa resetujú. Používajte ich len pre hodnoty, ktoré sa v každom
cykle prepočítajú nanovo.
class Report extends Component
{
public string $period = 'month';
// Po každej požiadavke sa vynuluje — neukladajte sem stav.
protected array $cache = [];
}5. wire:model a viazanie dát
wire:model viaže pole formulára na vlastnosť komponentu. V Livewire 3 je viazanie
predvolene odložené: hodnota neodchádza na server pri každom stlačení klávesy,
ale spolu s nasledujúcou akciou (klik, submit).
{{-- Odložene: hodnota pôjde s ďalšou požiadavkou --}}
<input type="text" wire:model="name">
{{-- Okamžite: požiadavka pri každej zmene --}}
<input type="text" wire:model.live="search">
{{-- S oneskorením 500 ms po dopísaní --}}
<input type="text" wire:model.live.debounce.500ms="search">
{{-- Najviac raz za 2 s, kým používateľ píše --}}
<input type="text" wire:model.live.throttle.2s="search">
{{-- Až pri strate fokusu --}}
<input type="text" wire:model.blur="email">
{{-- Vlastnosť sa mení okamžite na klientovi, server sa dozvie neskôr --}}
<input type="text" wire:model.lazy="draft">Modifikátory
| Modifikátor | Správanie | Kedy použiť |
|---|---|---|
| — | Odložené odoslanie | Bežné polia formulára |
.live | Požiadavka pri každej zmene | Živé vyhľadávanie, závislé selecty |
.blur | Požiadavka pri strate fokusu | Validácia poľa po opustení |
.debounce.Xms | Počkať na pauzu v písaní | Vyhľadávanie počas písania |
.throttle.Xs | Najviac raz za interval | Náročné dotazy |
.number | Previesť na číslo | Číselné polia |
.boolean | Previesť na boolean | Selecty áno/nie |
.fill | Vziať počiatočnú hodnotu z HTML | Predvyplnené formuláre |
Všetky typy polí
{{-- Text, textarea --}}
<input type="text" wire:model="title">
<textarea wire:model="body"></textarea>
{{-- Jeden checkbox: bool --}}
<input type="checkbox" wire:model="agreed">
{{-- Skupina checkboxov: 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">
{{-- Rádiové tlačidlá --}}
<input type="radio" value="card" wire:model="payment">
<input type="radio" value="cash" wire:model="payment">
{{-- Select --}}
<select wire:model.live="categoryId">
<option value="">Všetky kategórie</option>
@foreach ($categories as $category)
<option value="{{ $category->id }}">{{ $category->name }}</option>
@endforeach
</select>
{{-- Viacnásobný select --}}
<select wire:model="tagIds" multiple>
@foreach ($tags as $tag)
<option value="{{ $tag->id }}">{{ $tag->name }}</option>
@endforeach
</select>Vnorené dáta
Bodková notácia funguje pre polia aj pre vlastnosti modelov.
public array $form = [
'name' => '',
'address' => ['city' => '', 'street' => ''],
];
public Post $post;<input wire:model="form.name">
<input wire:model="form.address.city">
<input wire:model="post.title">wire:model="post.title" fungovalo, pole musí byť
povolené validačným pravidlom (rules alebo #[Validate]) — inak Livewire vyhodí
výnimku. Je to ochrana pred hromadným priradením.
6. Akcie (actions)
Akcia je verejná metóda komponentu volaná zo šablóny. Nahrádza obvyklú kombináciu
fetch() plus obslužný kód na serveri.
{{-- Klik --}}
<button wire:click="save">Uložiť</button>
{{-- Odoslanie formulára (submit je zachytený) --}}
<form wire:submit="save">
<input wire:model="title">
<button type="submit">Odoslať</button>
</form>
{{-- Klávesy --}}
<input wire:keydown.enter="search" wire:keydown.escape="clear">
{{-- Ďalšie DOM udalosti --}}
<div wire:mouseenter="preload">…</div>
<select wire:change="applyFilter">…</select>Parametre
<button wire:click="delete({{ $post->id }})">Zmazať</button>
<button wire:click="setStatus('published')">Publikovať</button>
<button wire:click="move({{ $item->id }}, 'up')">Hore</button>public function delete(int $postId): void
{
$post = Post::findOrFail($postId);
$this->authorize('delete', $post); // autorizáciu overujeme vždy
$post->delete();
$this->dispatch('notify', message: 'Príspevok bol zmazaný');
}delete(999) s ľubovoľným id. Kontrola oprávnení vnútri
metódy je povinná.
Model binding v parametroch
Livewire vie rozlúštiť modely podľa id rovnako ako router v Laraveli.
public function archive(Post $post): void
{
$this->authorize('update', $post);
$post->update(['archived_at' => now()]);
}Modifikátory akcií
{{-- Potvrdenie v prehliadači pred odoslaním --}}
<button wire:click="delete" wire:confirm="Naozaj zmazať? Akciu nemožno vrátiť.">Zmazať</button>
{{-- preventDefault / stopPropagation --}}
<a href="#" wire:click.prevent="open">Otvoriť</a>
<div wire:click.stop="select">…</div>
{{-- Spustí sa raz --}}
<button wire:click.once="init">Inicializovať</button>
{{-- Len ak sa kliklo priamo na tento prvok --}}
<div wire:click.self="close">…</div>Magické akcie
| Akcia | Čo robí |
|---|---|
$refresh | Znova vykreslí komponent bez zmeny stavu |
$set('prop', value) | Priradí vlastnosti hodnotu |
$toggle('prop') | Prepne boolean vlastnosť |
$dispatch('event') | Odošle Livewire udalosť |
$parent.method() | Zavolá metódu rodičovského komponentu |
<button wire:click="$refresh">Obnoviť</button>
<button wire:click="$set('tab', 'settings')">Nastavenia</button>
<button wire:click="$toggle('showFilters')">Filtre</button>
<button wire:click="$parent.closeModal()">Zavrieť</button>Presmerovania
public function store(): void
{
$this->validate();
$post = Post::create($this->only('title', 'body'));
session()->flash('status', 'Príspevok bol vytvorený');
$this->redirect(route('posts.show', $post), navigate: true);
}7. Životný cyklus požiadavky
Pochopenie poradia volaní odstráni 90 % „záhadných“ chýb. Rozlišujeme prvé vykreslenie (bežná HTTP požiadavka na stránku) a následné aktualizácie (AJAX požiadavky Livewire).
Prvé vykreslenie
1. Vytvorí sa inštancia komponentu
2. boot()
3. mount($params)
4. booted()
5. hydrate hooky sa NEvolajú
6. render()
7. HTML sa vloží do stránkyNásledná aktualizácia
1. Príde POST /livewire/update so snapshotom stavu
2. Overí sa kontrolný súčet snapshotu (ochrana pred podvrhnutím)
3. Vytvorí sa inštancia komponentu
4. boot()
5. Stav sa obnoví zo snapshotu
6. hydrate() a hydrateFoo() pre každú vlastnosť
7. booted()
8. updating($prop, $value) / updatingFoo($value) — pred priradením
9. Vlastnosti dostanú nové hodnoty
10. updated($prop, $value) / updatedFoo($value) — po priradení
11. Zavolajú sa metódy z frontu calls (akcie)
12. rendering()
13. render()
14. rendered($view, $html)
15. dehydrate() — stav sa zabalí späť do snapshotu
16. Odpoveď: nové HTML plus effects (udalosti, presmerovania, dispatch do prehliadača)Snapshot a kontrolný súčet
Celý stav komponentu cestuje na klienta a späť v atribúte wire:snapshot. Aby klient dáta
nepodvrhol, Livewire snapshot podpisuje HMAC podpisom odvodeným od APP_KEY. Pri nezhode
podpisu je požiadavka odmietnutá chybou „Livewire encountered corrupt data“.
render() alebo cez
#[Computed].
Čo sa deje s DOM
Livewire nenahrádza celý uzol, ale vykoná morph: prejde starý aj nový strom a zmení iba
rozdiely. Preto zostáva zachovaný fokus v poli, pozícia scrollu aj stav Alpine. Ak sa mení štruktúra
zoznamu, morph algoritmus potrebuje pomôcku — pozri wire:key v kapitole 10.
8. Hooky životného cyklu
Hooky umožňujú zasiahnuť do ktorejkoľvek fázy cyklu.
class ProductEditor extends Component
{
public Product $product;
public string $name = '';
public float $price = 0;
// Spustí sa na začiatku KAŽDEJ požiadavky, pred obnovením stavu.
public function boot(): void
{
// Vhodné miesto pre závislosti, ktoré sa nedajú serializovať.
}
// Len pri prvom vykreslení.
public function mount(Product $product): void
{
$this->product = $product;
$this->name = $product->name;
$this->price = $product->price;
}
// Po obnovení stavu, v každej ďalšej požiadavke.
public function hydrate(): void
{
}
// Po boot() a obnovení stavu.
public function booted(): void
{
}
// Pred zmenou ľubovoľnej vlastnosti.
public function updating(string $property, mixed $value): void
{
}
// Po zmene ľubovoľnej vlastnosti.
public function updated(string $property, mixed $value): void
{
$this->validateOnly($property);
}
// Len pre vlastnosť $price.
public function updatedPrice(mixed $value): void
{
$this->price = round((float) $value, 2);
}
// Pre vnorený kľúč 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
{
}
// Pred zabalením stavu do snapshotu.
public function dehydrate(): void
{
}
public function render()
{
return view('livewire.product-editor');
}
}Pravidlá pomenovania
| Vlastnosť | Hook |
|---|---|
$price | updatedPrice() |
$isActive | updatedIsActive() |
$form['email'] | updatedFormEmail() |
$post->title | updatedPostTitle() |
public function updatedSearch() { $this->resetPage(); }
9. Validácia a Form objekty
Livewire používa validátor Laravelu — pravidlá, správy aj lokalizácia sú rovnaké.
Spôsob 1: atribúty (Livewire 3)
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: 'Je potrebný súhlas so spracovaním údajov.')]
public bool $consent = false;
public function submit(): void
{
$data = $this->validate();
Contact::create($data);
$this->reset();
session()->flash('status', 'Správa bola odoslaná');
}
}Spôsob 2: metóda rules()
Potrebná vtedy, keď pravidlá závisia od stavu.
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' => 'Tento e-mail je už zaregistrovaný.',
];
}
protected function validationAttributes(): array
{
return [
'email' => 'e-mailová adresa',
];
}Živá validácia
public function updated(string $property): void
{
$this->validateOnly($property); // overujeme len zmenené pole
}<form wire:submit="submit" novalidate>
<label for="email">E-mail</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">Odoslať</span>
<span wire:loading wire:target="submit">Odosielame…</span>
</button>
</form>Ručná správa chýb
$this->addError('email', 'Doména je na čiernej listine.');
$this->resetValidation('email');
$this->resetValidation(); // vynulovať všetky
$this->validateOnly('email');
// Vyhodenie chyby ako v bežnom Laravel kontroléri
throw ValidationException::withMessages([
'code' => 'Nesprávny overovací kód.',
]);Form objekty
Keď formulár narastie, presuňte ho do samostatnej triedy. Komponent zostane tenký a pravidlá aj dáta formulára sa dajú znovu použiť pri vytváraní aj úprave.
php artisan livewire:form PostForm<?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'));
}
}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);
}
}<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">Uložiť</button>
</form>10. Vnorené komponenty a wire:key
Komponenty sa dajú vnárať. Každý vnorený komponent je nezávislá jednotka s vlastným stavom a vlastným aktualizačným cyklom: klik vnútri potomka rodiča nevykreslí znova.
<div>
<h1>Objednávky</h1>
@foreach ($orders as $order)
<livewire:order-row :order="$order" :key="'order-'.$order->id" />
@endforeach
</div>:key je v cykloch povinný. Bez jedinečného kľúča si morph algoritmus
prvky pomýli pri radení, filtrovaní alebo mazaní: hodnoty polí sa „presťahujú“ do susedných riadkov.
Kľúč musí byť stabilný — $loop->index nestačí, použite id záznamu.
Odovzdávanie dát nadol
Parametre sa odovzdávajú raz, v mount(). Pri ďalších vykresleniach rodiča sa potomok
automaticky neaktualizuje — žije vlastným životom.
use Livewire\Attributes\Reactive;
class OrderTotal extends Component
{
// S #[Reactive] hodnota príde od rodiča pri každom jeho vykreslení.
#[Reactive]
public int $quantity;
}Volanie rodiča
<button wire:click="$parent.refreshList()">Obnoviť zoznam</button>Obojsmerná väzba: #[Modelable]
Umožňuje použiť wire:model priamo na komponente — hodí sa pri widgetoch typu pole.
use Livewire\Attributes\Modelable;
class RatingInput extends Component
{
#[Modelable]
public int $value = 0;
public function set(int $value): void
{
$this->value = $value;
}
}{{-- V rodičovskej šablóne --}}
<livewire:rating-input wire:model.live="review.rating" />Podmienené vykreslenie vnorených komponentov
@if ($showDetails)
<livewire:order-details :order-id="$orderId" :key="'details-'.$orderId" />
@endif11. Udalosti
Udalosti spájajú komponenty, ktoré nie sú vo vzťahu rodič — potomok. V Livewire 3 sa odosielajú cez
dispatch() (vo verzii 2 to bolo emit()).
Odoslanie
// Jednoduchá udalosť
$this->dispatch('post-created');
// S pomenovanými parametrami
$this->dispatch('post-created', postId: $post->id, title: $post->title);
// Len konkrétnemu komponentu
$this->dispatch('refresh')->to(OrderList::class);
// Len sebe samému
$this->dispatch('recalculate')->self();{{-- Priamo zo šablóny --}}
<button wire:click="$dispatch('open-modal', { name: 'create-order' })">Nová objednávka</button>Prijatie
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];
}
// Dynamický názov udalosti
#[On('order-updated.{orderId}')]
public function onOrderUpdated(): void
{
$this->refreshList();
}
}Alternatíva je pole $listeners (kompatibilné s verziou 2):
protected $listeners = [
'post-created' => 'onPostCreated',
'refresh' => '$refresh',
];Udalosti do prehliadača
Livewire vie odoslať bežnú DOM udalosť — zachytí ju Alpine.js alebo váš vlastný JS. Je to správny spôsob, ako zobrazovať toasty a otvárať modálne okná.
$this->dispatch('notify', type: 'success', message: 'Objednávka bola uložená');<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>document.addEventListener('notify', (event) => {
console.log(event.detail.message);
});12. Vypočítané vlastnosti
#[Computed] poskytuje odvodené dáta bez toho, aby sa ukladali do stavu. Výsledok sa
cachuje počas jednej požiadavky, takže trojnásobné použitie v šablóne nespôsobí tri SQL dotazy.
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); // s DPH
}
}<div>
@foreach ($this->products as $product)
<div>{{ $product->name }} — {{ $this->items[$product->id] }} ks</div>
@endforeach
<p>Medzisúčet: {{ number_format($this->subtotal, 2, ',', ' ') }} €</p>
<p>Spolu s DPH: {{ number_format($this->total, 2, ',', ' ') }} €</p>
</div>$this. V šablóne je vypočítaná vlastnosť dostupná ako
$this->products, nie $products. Tým sa líši od bežných verejných
vlastností.
Cachovanie medzi požiadavkami
// Cache na 5 minút v spoločnej cache aplikácie
#[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'),
];
}
// Spoločná cache pre všetkých používateľov
#[Computed(cache: true, key: 'global-stats')]
public function globalStats(): array
{
return app(StatsService::class)->build();
}Zrušenie cache
public function addItem(int $productId): void
{
$this->items[$productId] = ($this->items[$productId] ?? 0) + 1;
unset($this->products, $this->subtotal, $this->total); // zahodiť cache
}Dáta v render() vs #[Computed]
Ani jedna možnosť neukladá dáta do stavu. Rozdiel je v dosahu: render() odovzdá premenné
iba šablóne, kým #[Computed] je dostupné v PHP metódach aj v šablóne a navyše sa cachuje.
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. Stav v URL a v relácii
Filtre, vyhľadávanie a aktívna záložka by mali prežiť obnovenie stránky a mali by sa dať zdieľať
odkazom. Atribút #[Url] synchronizuje vlastnosť s query stringom.
use Livewire\Attributes\Url;
class ProductCatalog extends Component
{
#[Url]
public string $search = '';
// Iný názov parametra v URL
#[Url(as: 'cat')]
public ?int $categoryId = null;
// Nezobrazovať v URL, kým sa hodnota rovná počiatočnej
#[Url(except: '')]
public string $sort = 'popular';
// Zachovať medzi prechodmi wire:navigate
#[Url(keep: true)]
public int $perPage = 24;
// Použiť history.pushState namiesto replaceState
#[Url(history: true)]
public string $tab = 'all';
}Výsledok: /catalog?search=notebook&cat=5&sort=price-asc&tab=sale
Ukladanie do relácie
use Livewire\Attributes\Session;
class Sidebar extends Component
{
#[Session]
public bool $collapsed = false;
#[Session(key: 'admin.table.density')]
public string $density = 'comfortable';
}Ručná práca s reláciou a flash správami
public function save(): void
{
$this->validate();
Setting::updateOrCreate(['key' => 'theme'], ['value' => $this->theme]);
session()->flash('status', 'Nastavenia boli uložené');
}@if (session('status'))
<div class="docs-note docs-note--tip">{{ session('status') }}</div>
@endif#[Url] vlastnosť pri zmene volá History
API. Pri poliach, ktoré sa menia s každým stlačením klávesy, kombinujte s
wire:model.live.debounce.
14. Nahrávanie súborov
Trait WithFileUploads pridáva plnohodnotné nahrávanie: súbor odchádza na server hneď po
výbere, uloží sa do dočasného úložiska a je dostupný ako objekt TemporaryUploadedFile.
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')] // do 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 bol aktualizovaný');
}
public function removeDocument(int $index): void
{
unset($this->documents[$index]);
$this->documents = array_values($this->documents);
}
}<form wire:submit="save">
<input type="file" wire:model="avatar" accept="image/*">
{{-- Priebeh nahrávania --}}
<div wire:loading wire:target="avatar" class="text-sm">Nahrávame súbor…</div>
{{-- Náhľad pred uložením --}}
@if ($avatar)
<img src="{{ $avatar->temporaryUrl() }}" alt="Náhľad" 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">Uložiť</button>
</form>Presný indikátor priebehu
<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>Nastavenie dočasného úložiska
'temporary_file_upload' => [
'disk' => 's3', // alebo null → predvolený 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, // minút do automatického čistenia
],upload_max_filesize,
post_max_size a max_execution_time v php.ini, ako aj
client_max_body_size v nginx musia byť aspoň také veľké ako vaše max:
v pravidlách. Inak sa nahrávanie preruší bez zrozumiteľnej chyby.
15. Stránkovanie
Trait WithPagination pripojí stránkovanie Laravelu bez obnovovania stránky.
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;
// Pri zmene filtrov sa vraciame na prvú stranu.
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),
]);
}
}<div>
<input type="search" wire:model.live.debounce.400ms="search" placeholder="Hľadať podľa čísla">
<table>
<thead>
<tr>
<th wire:click="sortBy('number')" style="cursor:pointer">Číslo</th>
<th wire:click="sortBy('created_at')" style="cursor:pointer">Dátum</th>
<th wire:click="sortBy('total')" style="cursor:pointer">Suma</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">Nenašli sa žiadne objednávky</td></tr>
@endforelse
</tbody>
</table>
{{ $orders->links() }}
</div>Užitočné detaily
// Vlastný názov parametra strany — treba, ak sú na stránke dva paginátory
protected string $paginationTheme = 'tailwind'; // alebo 'bootstrap'
public function render()
{
return view('livewire.dashboard', [
'orders' => Order::paginate(10, pageName: 'orders-page'),
'invoices' => Invoice::paginate(10, pageName: 'invoices-page'),
]);
}paginate() navyše dotaz
COUNT(*). Ak celkový počet strán nepotrebujete, použite simplePaginate() —
je citeľne lacnejší.
16. Stavy načítavania
Každá akcia je sieťová požiadavka. Bez indikácie pôsobí rozhranie zaseknuto. Livewire ponúka deklaratívne direktívy, ktoré nepotrebujú ani riadok JS.
{{-- Zobraziť počas ľubovoľnej požiadavky komponentu --}}
<div wire:loading>Načítava sa…</div>
{{-- Skryť počas požiadavky --}}
<div wire:loading.remove>Obsah</div>
{{-- Len pre konkrétnu akciu --}}
<button wire:click="save">Uložiť</button>
<span wire:loading wire:target="save">Ukladáme…</span>
{{-- Pre viacero cieľov --}}
<span wire:loading wire:target="save,delete,publish">Spracúvame…</span>
{{-- Pre konkrétnu vlastnosť --}}
<span wire:loading wire:target="search">Hľadáme…</span>
{{-- Vylúčiť cieľ --}}
<div wire:loading wire:target.except="search">Aktualizujeme…</div>Modifikátory
{{-- Zablokovať tlačidlo --}}
<button wire:click="save" wire:loading.attr="disabled">Uložiť</button>
{{-- Pridať CSS triedu --}}
<button wire:click="save" wire:loading.class="opacity-50 cursor-wait">Uložiť</button>
{{-- Odobrať triedu --}}
<div wire:loading.class.remove="bg-white">…</div>
{{-- Oneskorenie: indikátor len ak požiadavka trvá dlhšie než 300 ms --}}
<div wire:loading.delay>Načítava sa…</div>
{{-- Presné prahy: shortest 50ms, shorter 100ms, short 150ms,
default 200ms, long 300ms, longer 500ms, longest 1s --}}
<div wire:loading.delay.long>Načítava sa…</div>
{{-- Riadenie zobrazenia --}}
<div wire:loading.flex>…</div>
<div wire:loading.grid>…</div>
<div wire:loading.inline-flex>…</div>Neuložené zmeny: wire:dirty
<input wire:model="title">
<span wire:dirty wire:target="title" class="text-amber-600">Máte neuložené zmeny</span>
<button wire:click="save" wire:dirty.class="ring-2 ring-amber-400">Uložiť</button>Strata spojenia: wire:offline
<div wire:offline class="banner banner--warn">
Nie ste pripojení k serveru. Zmeny sa neukladajú.
</div>Skeleton pri prvom načítaní
<div wire:init="loadHeavyData">
@if ($loaded)
{{-- skutočné dáta --}}
@else
<div class="skeleton h-40 w-full animate-pulse bg-gray-200"></div>
@endif
</div>18. Lenivé načítanie a polling
Lenivé načítanie komponentu
Náročný komponent netreba vykresľovať v prvej odpovedi: stránka sa odovzdá okamžite so zástupným obsahom a telo sa dotiahne druhou požiadavkou.
<livewire:revenue-chart lazy />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(), // náročný dotaz
]);
}
}
Predvolene sa lenivý komponent načíta hneď po vykreslení stránky. Variant
#[Lazy(isolate: false)] spojí požiadavky viacerých lenivých komponentov do jednej,
a lazy="on-load" / lazy="on-scroll" riadia moment načítania.
{{-- Načítať, keď sa blok dostane do viewportu --}}
<livewire:revenue-chart lazy="on-scroll" />Odložená inicializácia: wire:init
<div wire:init="loadStats">
@if ($stats)
…
@else
<div class="skeleton"></div>
@endif
</div>Dopytovanie servera: wire:poll
{{-- Obnovovať každé 2 s (predvolená hodnota je 2500 ms) --}}
<div wire:poll>…</div>
{{-- Vlastný interval --}}
<div wire:poll.5s>…</div>
<div wire:poll.750ms>…</div>
{{-- Volať konkrétnu metódu --}}
<div wire:poll.10s="refreshQueue">…</div>
{{-- Zastaviť dopytovanie, keď je záložka neaktívna --}}
<div wire:poll.visible.5s="refreshQueue">…</div>
{{-- Zastaviť po 5 minútach nečinnosti používateľa --}}
<div wire:poll.keep-alive.5s>…</div>wire:poll.2s na stránke otvorenej u 100 zamestnancov
znamená 3000 požiadaviek za minútu na váš PHP proces. Vždy pridajte .visible, zväčšite
interval na rozumnú hodnotu a pri skutočne živých dátach zvážte websockety (Laravel Echo + Reverb).
19. Alpine.js, $wire a JS hooky
Alpine.js je súčasťou Livewire 3. Vnútri komponentu je dostupný objekt $wire — proxy
k stavu a metódam PHP komponentu priamo z JavaScriptu.
<div x-data="{ open: false }">
{{-- Čisto klientský stav: server sa nezapája --}}
<button x-on:click="open = !open">Detaily</button>
<div x-show="open" x-transition>
{{-- Čítanie vlastnosti komponentu --}}
<p x-text="$wire.title"></p>
{{-- Zápis vlastnosti --}}
<button x-on:click="$wire.title = 'Nový nadpis'">Premenovať</button>
{{-- Volanie metódy (vracia Promise) --}}
<button x-on:click="$wire.save()">Uložiť</button>
{{-- Metóda s čakaním na výsledok --}}
<button x-on:click="await $wire.calculate(); open = false">Prepočítať</button>
{{-- Volanie bez opätovného vykreslenia --}}
<button x-on:click="$wire.$set('tab', 'stats', false)">Štatistiky</button>
</div>
</div>Obojsmerná väzba: $wire.entangle
<div x-data="{ query: $wire.entangle('search') }">
<input x-model="query">
<p x-show="query.length > 0">Hľadáme: <span x-text="query"></span></p>
</div>
{{-- Odložená synchronizácia: neposielať požiadavku pri každom znaku --}}
<div x-data="{ query: $wire.entangle('search').live }">…</div>Vlastné skripty vnútri komponentu
@script
<script>
// Spustí sa raz pri inicializácii komponentu.
const chart = new Chart(document.getElementById('sales'), {
type: 'line',
data: @json($chartData),
});
// $wire je dostupný aj tu.
$wire.on('data-updated', ({ points }) => {
chart.data.datasets[0].data = points;
chart.update();
});
</script>
@endscript{{-- Načítať externú knižnicu raz pre celú stránku --}}
@assets
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
@endassetsIgnorovanie podstromu
Ak si widget tretej strany spravuje DOM sám, morph algoritmus mu iba prekáža.
<div wire:ignore>
<select id="select2-field">…</select>
</div>
{{-- Ignorovať len potomkov, ale aktualizovať atribúty samotného prvku --}}
<div wire:ignore.self>…</div>Globálne JS hooky
document.addEventListener('livewire:init', () => {
// Pred odoslaním každej požiadavky
Livewire.hook('request', ({ uri, options, payload, respond, succeed, fail }) => {
options.headers['X-Tenant'] = window.tenantId;
succeed(({ status, json }) => {
console.debug('Livewire odpovedal', status);
});
fail(({ status, preventDefault }) => {
if (status === 419) {
preventDefault();
window.location.reload(); // vypršal CSRF token
}
});
});
// Pred a po morphe jednotlivého prvku
Livewire.hook('morph.updated', ({ el, component }) => {});
// Komponent bol inicializovaný
Livewire.hook('component.init', ({ component }) => {});
});
// Programový prístup ku komponentom
Livewire.dispatch('refresh-orders');
Livewire.find('component-id').call('save');
Livewire.all().forEach((component) => component.$refresh());20. Testovanie
Livewire ponúka vlastné testovacie API nad Laravelom. Testy sú rýchle — prehliadač netreba, funguje bežný PHPUnit alebo Pest.
<?php
use App\Livewire\Counter;
use Livewire\Livewire;
it('zvyšuje počítadlo', function () {
Livewire::test(Counter::class)
->assertSet('count', 0)
->call('increment')
->assertSet('count', 1)
->call('increment')
->assertSet('count', 2)
->call('decrement')
->assertSet('count', 1);
});use App\Livewire\ContactForm;
use App\Models\Contact;
use Livewire\Livewire;
it('validuje povinné polia', function () {
Livewire::test(ContactForm::class)
->set('name', '')
->set('email', 'nie-je-email')
->call('submit')
->assertHasErrors([
'name' => 'required',
'email' => 'email',
])
->assertNoRedirect();
});
it('uloží správne vyplnenú požiadavku', function () {
Livewire::test(ContactForm::class)
->set('name', 'Irena Kováčová')
->set('email', 'irena@example.com')
->set('message', str_repeat('Potrebujeme web pre kliniku. ', 3))
->set('consent', true)
->call('submit')
->assertHasNoErrors()
->assertDispatched('notify');
expect(Contact::where('email', 'irena@example.com')->exists())->toBeTrue();
});Hlavné asercie
| Metóda | Overuje |
|---|---|
assertSet('prop', $value) | Hodnotu vlastnosti |
assertNotSet('prop', $value) | Že hodnota je iná |
assertSee('text') | Že text je v HTML |
assertDontSee('text') | Že text chýba |
assertSeeHtml('<b>') | Že značkovanie je prítomné |
assertHasErrors(['email']) | Chyby validácie |
assertHasNoErrors() | Že chyby nie sú |
assertDispatched('event') | Že udalosť bola odoslaná |
assertRedirect(route(…)) | Že prebehlo presmerovanie |
assertStatus(403) | HTTP status |
assertForbidden() | Zamietnutý prístup |
assertCount('items', 3) | Veľkosť poľa alebo kolekcie |
Autentifikácia, parametre, udalosti
// V mene používateľa
Livewire::actingAs($admin)
->test(OrderTable::class)
->assertSee('Všetky objednávky');
// S parametrami mount()
Livewire::test(OrderCard::class, ['order' => $order, 'mode' => 'full'])
->assertSee($order->number);
// Prijatie udalosti
Livewire::test(OrderList::class)
->dispatch('order-created', orderId: 42)
->assertSee('Objednávka č. 42');
// Nahranie súboru
use Illuminate\Http\UploadedFile;
Livewire::test(AvatarUploader::class)
->set('avatar', UploadedFile::fake()->image('avatar.jpg', 400, 400))
->call('save')
->assertHasNoErrors();
// Komponent vnútri stránky
$this->get('/dashboard')
->assertSeeLivewire(Dashboard::class)
->assertDontSeeLivewire(AdminPanel::class);21. Bezpečnosť
1. Autorizácia v každej akcii
public function delete(int $postId): void
{
$post = Post::findOrFail($postId);
$this->authorize('delete', $post); // Policy — povinne
$post->delete();
}// Autorizácia celého komponentu
public function mount(Project $project): void
{
$this->authorize('view', $project);
$this->project = $project;
}2. #[Locked] pre identifikátory
use Livewire\Attributes\Locked;
class InvoiceEditor extends Component
{
#[Locked]
public int $invoiceId; // z frontendu sa nedá podvrhnúť
public string $note = '';
}Bez #[Locked] stačí jeden riadok v konzole prehliadača:
Livewire.find('...').set('invoiceId', 999) // cudzia faktúra3. Pravidlá pre viazanie na modely
wire:model="post.title" funguje len vtedy, ak je pole povolené validačným pravidlom —
takto Livewire chráni pred hromadným priradením.
protected function rules(): array
{
return [
'post.title' => 'required|string|max:180',
'post.body' => 'required|string',
// 'post.user_id' zámerne NEuvádzame — inak by sa dal príspevok prepísať na iného autora
];
}4. Escapovanie výstupu
{{-- Bezpečné: escapuje sa --}}
{{ $comment->body }}
{{-- Nebezpečné: surové HTML od používateľa --}}
{!! $comment->body !!}
{{-- Ak HTML potrebujete, sanitizujte na serveri --}}
{!! clean($comment->body) !!}5. Obmedzenie frekvencie
use Illuminate\Support\Facades\RateLimiter;
public function login(): void
{
$key = 'login:' . request()->ip();
if (RateLimiter::tooManyAttempts($key, 5)) {
throw ValidationException::withMessages([
'email' => 'Príliš veľa pokusov. Skúste to o minútu.',
]);
}
RateLimiter::hit($key, 60);
// …
}6. Neukladajte tajomstvá do vlastností
class PaymentForm extends Component
{
public string $cardLast4 = ''; // v poriadku
// NIE: pôjde do prehliadača v čitateľnej podobe vnútri wire:snapshot
// public string $apiSecret = '';
// public string $fullCardNumber = '';
protected function gateway(): PaymentGateway
{
return app(PaymentGateway::class); // tajomstvá zostanú na serveri
}
}Kontrolný zoznam pred nasadením
$this->authorize()je v každej akcii, ktorá mení dáta.- Všetky identifikátory sú označené
#[Locked]. - Validačné pravidlá neobsahujú služobné polia (
user_id,role,price). - Verejné vlastnosti neobsahujú tokeny, kľúče ani zbytočné osobné údaje.
- Nahrávanie súborov je obmedzené podľa MIME typov a veľkosti.
- Prihlasovacie a kontaktné formuláre sú chránené rate limitingom.
{!! !!}sa používa len pre dôveryhodné alebo sanitizované HTML.
22. Výkon
1. Minimum stavu vo verejných vlastnostiach
public Collection $products; // 500 modelov cestuje na klienta a späť
public function mount(): void
{
$this->products = Product::with('category', 'images')->get();
}public function render()
{
return view('livewire.catalog', [
'products' => Product::with('category')->paginate(24),
]);
}2. Neposielajte požiadavku pri každom stlačení klávesy
{{-- Zle: požiadavka pri každom znaku --}}
<input wire:model.live="search">
{{-- Dobre --}}
<input wire:model.live.debounce.400ms="search">3. Rozdeľte náročné stránky na komponenty
Aktualizácia vnoreného komponentu rodiča znova nevykreslí. Dashboard zo šiestich nezávislých widgetov beží citeľne svižnejšie než jeden monolit.
4. Cachujte drahé výpočty
#[Computed(persist: true, seconds: 600)]
public function monthlyRevenue(): array
{
return app(RevenueService::class)->byMonth();
}5. Sledujte N+1
// Zle — dotaz na každý riadok v šablóne
$orders = Order::paginate(50);
// Dobre
$orders = Order::with(['customer', 'items.product'])->paginate(50);Zapnite prísny režim vo vývoji, aby N+1 spadlo s chybou:
public function boot(): void
{
Model::preventLazyLoading(! app()->isProduction());
}6. wire:key v zoznamoch
Bez kľúčov prestavuje morph algoritmus viac uzlov, než treba — a robí to nesprávne.
7. Lenivé načítanie náročných blokov
<livewire:revenue-chart lazy="on-scroll" />8. Opatrne s pollingom
wire:poll.visible.10s namiesto wire:poll ušetrí serveru rád veľkosti
požiadaviek.
| Príznak | Pravdepodobná príčina |
|---|---|
| Oneskorenie 300–800 ms pri každej akcii | Náročný render() alebo N+1 |
| Obrovské HTML stránky | Kolekcie modelov vo verejných vlastnostiach |
| Rastúca záťaž PHP-FPM | Agresívny wire:poll alebo .live bez debounce |
| Polia „skáču“ pri aktualizácii zoznamu | Chýba wire:key |
| Prekresľuje sa celá stránka | Všetko v jednom komponente, chýba rozdelenie |
23. Časté chyby a riešenia
„Component must have a single root element“
V šablóne je viac koreňových uzlov alebo text či komentár na najvyššej úrovni. Zabaľte všetko do jedného <div>.
<h1>Nadpis</h1>
<p>Text</p><div>
<h1>Nadpis</h1>
<p>Text</p>
</div>„Livewire encountered corrupt data“
Nesedí podpis snapshotu. Príčiny: zmenil sa APP_KEY, stránka je otvorená z cache po
nasadení, dve záložky s rôznymi reláciami. Zvyčajne pomôže obnovenie stránky; po nasadení
php artisan optimize:clear.
Kliknutia nefungujú, konzola je prázdna
- V layoute chýba
@livewireScripts. - Je pripojená druhá inštancia Alpine.js (v Livewire 3 je Alpine už vnútri).
- Chyba JS vyššie na stránke prerušila vykonávanie.
- Prvok je vnútri
wire:ignore.
Hodnoty polí sa „presúvajú“ medzi riadkami
V cykle chýba wire:key alebo :key. Kľúč musí byť stabilný a jedinečný.
@foreach ($rows as $row)
<div wire:key="row-{{ $row->id }}">…</div>
@endforeach„Unable to set component data. Public property not found“
Naviazali ste wire:model na neexistujúcu alebo neverejnú vlastnosť. Skontrolujte zápis
a modifikátor prístupu.
„Cannot bind to model data without validation rules“
Väzba typu wire:model="post.title" vyžaduje pravidlo pre post.title
v rules() alebo #[Validate].
Widget tretej strany sa po aktualizácii rozpadne
Select2, Flatpickr, TinyMCE a podobné si menia DOM samy. Zabaľte ich do wire:ignore
a synchronizujte ručne.
<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>Nahrávanie súboru ticho zlyhá
Skontrolujte upload_max_filesize a post_max_size v PHP, ako aj
client_max_body_size v nginx. Hodnoty musia prevyšovať limit v pravidle max:.
Modálne okno sa po uložení nezavrie
Odošlite udalosť do prehliadača a spracujte ju v Alpine namiesto spoliehania sa na prekreslenie.
$this->dispatch('close-modal', name: 'order-form');Skripty po wire:navigate nefungujú
Presuňte inicializáciu z DOMContentLoaded do livewire:navigated.
Chyba 419 (Page Expired)
Vypršala relácia. Zvýšte SESSION_LIFETIME alebo zachyťte status v hooku
request a stránku obnovte — príklad je v kapitole 19.
24. Ťahák
Blade direktívy
| Direktíva | Účel |
|---|---|
wire:model | Viazanie poľa na vlastnosť (odložené) |
wire:model.live | Viazanie s požiadavkou pri každej zmene |
wire:model.blur | Odoslanie pri strate fokusu |
wire:click | Volanie metódy pri kliknutí |
wire:submit | Spracovanie odoslania formulára |
wire:keydown.enter | Reakcia na klávesu |
wire:change | Reakcia na change |
wire:confirm | Potvrdenie pred akciou |
wire:loading | Indikátor načítavania |
wire:target | Zúženie indikátora na akciu |
wire:dirty | Existujú neuložené zmeny |
wire:offline | Bez spojenia |
wire:poll | Periodická aktualizácia |
wire:init | Zavolať metódu hneď po vykreslení |
wire:navigate | SPA prechod cez odkaz |
wire:key | Identifikátor prvku v cykle |
wire:ignore | Vyňať podstrom z morphu |
wire:transition | Animácia zobrazenia a skrytia |
wire:stream | Streamovanie obsahu |
PHP atribúty
| Atribút | Účel |
|---|---|
#[Validate] | Validačné pravidlo vlastnosti |
#[Locked] | Zákaz zmeny z frontendu |
#[Computed] | Vypočítaná vlastnosť s cache |
#[Url] | Synchronizácia s query stringom |
#[Session] | Uloženie hodnoty do relácie |
#[On] | Poslucháč udalosti |
#[Reactive] | Aktualizácia parametra od rodiča |
#[Modelable] | Podpora wire:model na komponente |
#[Lazy] | Lenivé načítanie komponentu |
#[Layout] | Layout pre full-page komponent |
#[Title] | Titulok stránky |
#[Renderless] | Metóda bez prekreslenia |
Užitočné metódy komponentu
$this->reset(); // vrátiť všetky vlastnosti na počiatočné hodnoty
$this->reset('search', 'page'); // vrátiť vybrané
$this->only('title', 'body'); // pole z časti vlastností
$this->except('password'); // všetko okrem uvedeného
$this->fill(['title' => 'Nový']); // hromadné priradenie
$this->pull('draft'); // získať a vynulovať
$this->validate();
$this->validateOnly('email');
$this->resetValidation();
$this->addError('email', 'Správa');
$this->dispatch('saved', id: $post->id);
$this->redirect('/orders', navigate: true);
$this->redirectRoute('orders.index', navigate: true);
$this->skipRender(); // v tomto cykle neprekresľovať
$this->js('alert("Hotovo")'); // spustiť JS na klientovi
$this->stream(to: 'answer', content: $chunk);Artisan príkazy
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 --assetsOficiálne zdroje
- Dokumentácia Livewire: livewire.laravel.com/docs
- Dokumentácia Laravel: laravel.com/docs
- Alpine.js: alpinejs.dev