Livewire Lifecycle Hooks and Computed Properties

+15 Mana ✨

Introduction

Livewire components don't live for a single page load — they are serialized to the browser, re-hydrated on every interaction, and can re-render many times. Lifecycle hooks let you run code at precise moments in that cycle, and computed properties let you derive values without hammering the database on every render.

Key Concepts

  • Mount: Runs once when the component is first instantiated. Use it to accept parameters and set initial state.
  • Hydrate / Dehydrate: Run before/after the component is serialized to and from the browser on each round trip.
  • Updating / Updated: Run before and after a public property changes via wire:model. The most common place to validate or react to input.
  • Computed Property: A method decorated with #[Computed] that Livewire evaluates lazily and caches for the duration of a single request.

Real World Context

Without computed properties, accessing $this->posts inside a render() method and again inside a child template runs two identical database queries per interaction. On a busy dashboard that's the difference between 50ms and 500ms round trips.

Deep Dive

The Lifecycle in Order

Here's what happens when a user types in a wire:model.live input:

  1. Browser sends the new value to the server.
  2. Livewire instantiates the component class.
  3. hydrate() runs (if defined), rebuilding state from the serialized payload.
  4. updating('search') runs before the property is updated — throw an exception from here to abort the update.
  5. The public $search property is updated.
  6. updated('search') runs.
  7. render() returns the updated HTML.
  8. dehydrate() runs before the component is re-serialized.
  9. The server sends the HTML diff back to the browser.

Mount vs Constructor

The mount() method replaces the PHP constructor for Livewire components. It receives route parameters and initial data:

php
use Livewire\Component;
use App\Models\Post;

class EditPost extends Component
{
    public Post $post;
    public string $title = '';

    public function mount(Post $post): void
    {
        $this->post = $post;
        $this->title = $post->title;
    }

    public function render()
    {
        return view('livewire.edit-post');
    }
}

Mount only runs once per component lifetime — not on every AJAX round trip. Subsequent requests rehydrate $post and $title automatically from the serialized payload.

Reacting to Property Changes

The updating and updated hooks fire on every property write via wire:model:

php
public function updating(string $name, mixed $value): void
{
    // Validate any field as it changes
    if ($name === 'email') {
        $this->validateOnly('email');
    }
}

public function updatedSearch(string $value): void
{
    // Hook for a single specific property
    $this->resetPage();  // Reset pagination when search changes
}

Naming the method updatedSearch targets only the $search property — a cleaner pattern than inspecting the $name argument.

Computed Properties

A computed property runs lazily and caches its result for the current request:

php
use Livewire\Attributes\Computed;

class UserDashboard extends Component
{
    #[Computed]
    public function recentPosts()
    {
        return auth()->user()->posts()
            ->latest()
            ->limit(5)
            ->get();
    }

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

In the Blade template:

blade
<ul>
    @foreach ($this->recentPosts as $post)
        <li>{{ $post->title }}</li>
    @endforeach
</ul>

Access the property via $this->recentPosts in the template or PHP — the first call runs the query, every subsequent call returns the cached result. When a new request arrives (e.g. the user types a character), the cache resets and the query runs exactly once.

Common Pitfalls

  1. Heavy work inside render() — The render method runs on every round trip. Anything expensive should move to a computed property or be triggered by a specific action.
  2. Mutating state inside updating() — updating runs before the property is set. If you modify $this->search here, your change is overwritten. Use updated() for post-update logic.
  3. Forgetting that mount doesn't re-run — Users will tell you the route param vanished after an interaction. The fix is to put logic in mount that restores state from public properties, not to fetch it fresh every request.

Best Practices

  1. Prefer per-property hooks — updatedSearch() is clearer than inspecting $name in a generic updated() method.
  2. Use #[Computed] for derived data — If a value is always derived from other properties, make it computed. Livewire caches it per request automatically.
  3. Keep mount focused on initialization — Set state, load fixtures, nothing more. Complex logic belongs in actions.

Summary

  • mount runs once at component creation; hydrate/dehydrate wrap every round trip.
  • updating($name) / updated($name) react to property writes; updatedX targets a single property.
  • Computed properties (#[Computed]) cache derived values for the current request.
  • Keep render() lean — move heavy queries into computed properties or actions.
✓ Completed