Livewire

Livewire Best Practices

Best Practices for Livewire v3.

Most usefull tips when working with Livewire.
Official Livewire documentation.

Always use a single root element in your Livewire components:

❌ Bad:

<h1>Component Name</h1>
<div class="content">Content</div>

✅ Good:

<div>
    <h1>Component Name</h1>
    <div class="content">Content</div>
</div>

Especially with loops, it is important to provide component keys, as otherwise the DOM diff does not work correctly, leading to some nice effects. If something in the frontend is not working, it is usually due to the missing wire:key.

You can use wire:key="{{ $id }}" or the shorthand :key="$id".

❌ Bad:

@foreach($items as $item)
  <livewire:my-component :$item>
@endforeach

✅ Good:

@foreach($items as $item)
  <livewire:my-component :$item wire:key="{{ $item->id }}">
@endforeach

Keep your Livewire properties slim. Int, string, array... No large objects! Otherwise, they will be serialized and passed along with each request.

Note: If you are using Full-Page Components, it is recommended to fetch objects in the full-page component itself and then pass them down as primitive types to the underlying components.

Avoid deep nesting of Livewire components (0 containing 1) to prevent DOM diffing issues. Instead, favor Blade components for nesting. Blade components can communicate with the parent Livewire component without the additional overhead of Livewire.

Example

<div> <!–– level 0 ––>
    <h1>Component</h1>
    <livewire:profile :user="auth()->user()->uuid" /> <!–– level 1 ––>
</div>

Pass only an ID to the mount method, then assign the model attributes to the component properties. Remember: Do not assign the whole model, only its attributes.

Example mount:

public function mount(User $user): void
{
    $this->fill($user);
}

❌ Bad:

<livewire:profile :user="auth()->user()" />

✅ Good:

<livewire:profile :user="auth()->user()->id" />

Avoid situations that could lead to passing sensitive data to Livewire components, as they are easily accessible from the client side by default. You can hide properties on the frontend side by using the #[Locked] attribute starting from Livewire version 3.

You can use computed properties to avoid unnecessary database queries. Computed properties are cached during the component's lifecycle and do not result in additional SQL queries when called multiple times in the component class or in the Blade view.

❌ Bad:

class Cart extends Component
{
    public $cartItems = [];

		public function render() {
				return view('cart', ['cartItems' => $this->cartItems]);
		}
}

✅ Good:

#[Computed]
public function cartItems(): Collection
{
    return $this->cartService->getItems();
}

Instead of blocking the rendering of the page until your data is ready, you can create a placeholder using the technique of "Lazy Loading" to make your user interface feel more responsive.

Example:

<livewire:component-name lazy />

You can synchronize your data with the backend by using the @entangle directive. This way, the model is updated immediately on the user interface, and the data remains on the server after the network request reaches the server. This significantly improves the user experience on slow devices.

Example:

<div x-data="{ count: @entangle('count') }">
    <input x-model="count" type="number">
    <button @click="count++">+</button>
</div>

If you want to make the data binding even more flexible, you can use x-modelable from Alpine.js:

<div x-data="{ count: 0 }" x-modelable="count" x-model="count">
    <input x-model="count" type="number">
    <button @click="count++">+</button>
</div>

Livewire component:

public function submit(): void
{
    try {
        $this->validate();
    } catch (\Throwable $e) {
        $this->dispatch('validationError');
        throw $e;
    }
}

View:

@script
<script>
    Livewire.on('validationError', () => {
        window.scrollTo({top: 0, behavior: 'smooth'})
    })
</script>
@endscript