Lifecycle
Every LiVue component goes through a well-defined lifecycle. You can hook into each phase using instance hooks on the component itself, or observe it externally using Laravel-based Lifecycle Events.
Lifecycle Overview
The lifecycle differs between the initial page render and subsequent AJAX requests. Understanding this flow is key to knowing when each hook fires.
boot()
|
mount($params)
|
rendering() -> render view -> rendered()
boot()
|
setState() -> updating/updated per property
|
hydrate()
|
callMethod()
|
dehydrate()
|
rendering() -> render view -> rendered()
Tip — mount() runs only once (initial render), while hydrate() runs only on subsequent AJAX requests. boot() runs on every request.
Instance Hooks
Instance hooks are optional methods you define directly on your component class. They let you run code at specific moments during the component's lifecycle.
| Hook | When it runs |
|---|---|
boot() |
Every request (initial mount and subsequent AJAX) |
mount(...$params) |
First render only. Receives props/route parameters. |
hydrate() |
Subsequent AJAX requests, after setState() |
dehydrate() |
Before state serialization on every request |
updating($key, $value) |
Before a property is updated. Can modify the value by returning it. |
updated($key, $value) |
After a property has been updated |
rendering($view, $data) |
Before rendering. Can add extra data by returning modified array. |
rendered($view, $html) |
After rendering. Can modify the HTML output. |
Example
class UserEditor extends Component
{
public string $name = '';
public string $email = '';
public function boot(): void
{
// Runs on every request
}
public function mount(User $user): void
{
$this->name = $user->name;
$this->email = $user->email;
}
public function hydrate(): void
{
// Rebuild non-serializable objects
$this->service = app(MyService::class);
}
public function updatingEmail(string $value): string
{
// Normalize before setting
return strtolower(trim($value));
}
public function updatedEmail(string $value): void
{
// React after the value changed
$this->checkAvailability($value);
}
public function dehydrate(): void
{
// Cleanup before serialization
unset($this->temporaryData);
}
}
Property-Specific Hooks
You can define hooks for specific properties using PascalCase naming. The generic hook runs first, then the property-specific one.
public string $search = '';
public array $items = [];
// Called before $search is updated
public function updatingSearch(string $value): string
{
return trim($value);
}
// Called after $search is updated
public function updatedSearch(string $value): void
{
$this->resetPage();
}
// Array properties also receive the key
public function updatedItems(mixed $value, ?string $key): void
{
if ($key !== null) {
// Specific item changed
}
}
| Property | Hook method |
|---|---|
$email |
updatedEmail() |
$userName |
updatedUserName() |
$user_name |
updatedUserName() |
Trait-Based Hooks
Traits can define their own lifecycle hooks using the pattern {hook}{TraitName}().
Trait hooks run before the component's own hooks.
trait WithSearch
{
public string $search = '';
public function bootWithSearch(): void
{
// Called during boot()
}
public function mountWithSearch(): void
{
$this->search = request()->query('q', '');
}
public function hydrateWithSearch(): void
{
// Called during hydrate()
}
}
// Usage
class ProductList extends Component
{
use WithSearch;
// Trait hooks are called automatically
}
Execution Order
1. boot{TraitName}() for each trait
2. boot() component
3. setState() — hydrate state from snapshot
4. mount{TraitName}() for each trait (initial render only)
5. mount() component (initial render only)
6. hydrate{TraitName}() for each trait (AJAX only)
7. hydrate() component (AJAX only)
8. updating{TraitName}() → updating() → updatingProperty()
9. property update
10. updated{TraitName}() → updated() → updatedProperty()
11. rendering() → render view → rendered()
12. dehydrate()
Lifecycle Events
Beyond instance hooks, LiVue supports Lifecycle Events based on the Laravel event dispatcher.
This follows the same pattern as Eloquent's HasEvents —
you can observe a component's lifecycle from outside the component itself, using closures, observer classes, or the #[ObservedBy] attribute.
| Before (halting) | After (notification) | Callback arguments |
|---|---|---|
booting |
booted |
($component) |
mounting |
mounted |
($component) |
hydrating |
hydrated |
($component) |
calling |
called |
($component, $method, $params) |
dehydrating |
dehydrated |
($component) |
onRendering |
onRendered |
($component) |
| — | exception |
($component, $exception) |
Register event listeners using static methods in a service provider's boot() method:
use App\LiVue\Counter;
// In a ServiceProvider::boot()
Counter::mounting(function ($component) {
Log::info("Counter is about to mount");
});
Counter::mounted(function ($component) {
Log::info("Counter mounted, count = {$component->count}");
});
Counter::calling(function ($component, $method, $params) {
Log::info("Calling {$method} on Counter");
});
Tip — The onRendering() and onRendered() static methods use the on prefix to avoid conflicts with instance hooks. In observer classes, use the plain names rendering() and rendered().
Halting Events
The "before" events (booting, mounting, calling, etc.) are halting: if a callback returns false, the lifecycle phase is cancelled.
Counter::calling(function ($component, $method, $params) {
if ($method === 'delete' && ! $component->canDelete) {
return false; // blocks the call
}
});
Counter::mounting(function ($component) {
if (! auth()->check()) {
return false; // prevents mounting
}
});
Observers
Group related lifecycle listeners into an observer class — just like Eloquent observers. Define methods matching the event names and register the observer.
class CounterObserver
{
public function booting($component): void
{
// Before boot
}
public function mounted($component): void
{
// After mount
}
public function calling($component, $method, $params): void
{
Log::info("Calling {$method}");
}
public function exception($component, $exception): void
{
report($exception);
}
// In observers, use plain names (no "on" prefix)
public function rendering($component): void { }
public function rendered($component): void { }
}
Register the observer manually:
// Single observer
Counter::observe(CounterObserver::class);
// Instance
Counter::observe(new CounterObserver());
// Multiple observers
Counter::observe([CounterObserver::class, AnotherObserver::class]);
#[ObservedBy]
Declare the observer directly on the component class using the #[ObservedBy] attribute. It is repeatable, so you can attach multiple observers.
use LiVue\Attributes\ObservedBy;
#[ObservedBy(CounterObserver::class)]
class Counter extends Component
{
// ...
}
#[ObservedBy(AuditObserver::class)]
#[ObservedBy(MetricsObserver::class)]
class Order extends Component
{
// ...
}
Custom Event Classes
Map lifecycle events to custom Laravel event classes using the $dispatchesEvents property. This is useful for queue integration and listener decoupling.
class CounterMounted
{
public function __construct(public Component $component) {}
}
class Counter extends Component
{
protected array $dispatchesEvents = [
'mounted' => CounterMounted::class,
];
}
Event::listen(CounterMounted::class, function (CounterMounted $event) {
Log::info("Counter mounted: " . $event->component->getId());
});