v1 LiVue v1 is here — server-driven reactivity for Laravel using Vue.js Get Started →

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.

Initial Render
boot()
  |
mount($params)
  |
rendering() -> render view -> rendered()
Subsequent AJAX Requests
boot()
  |
setState() -> updating/updated per property
  |
hydrate()
  |
callMethod()
  |
dehydrate()
  |
rendering() -> render view -> rendered()

Tipmount() 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

app/LiVue/UserEditor.php
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.

Property-specific hooks
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.

WithSearch trait
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

Hook 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:

AppServiceProvider.php
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.

Blocking a method call
Counter::calling(function ($component, $method, $params) {
    if ($method === 'delete' && ! $component->canDelete) {
        return false; // blocks the call
    }
});
Preventing mount
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.

app/Observers/CounterObserver.php
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:

AppServiceProvider.php
// 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.

Single observer
use LiVue\Attributes\ObservedBy;

#[ObservedBy(CounterObserver::class)]
class Counter extends Component
{
    // ...
}
Multiple observers (repeatable)
#[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.

Custom event class
class CounterMounted
{
    public function __construct(public Component $component) {}
}
Component with $dispatchesEvents
class Counter extends Component
{
    protected array $dispatchesEvents = [
        'mounted' => CounterMounted::class,
    ];
}
Listening for the custom event
Event::listen(CounterMounted::class, function (CounterMounted $event) {
    Log::info("Counter mounted: " . $event->component->getId());
});