Livewire Best Practices
- 🌳 Single Root Element
- 🔑 wire:key is key!
- ✨ Performance
- 🌿 Component nesting max 1 level
- 🔮 Use Route Model Binding
- 🕵️ Do not pass sensitive data to components
- 🔗 Entangle
- Livewire Tips
Best Practices for Livewire v3.
Most usefull tips when working with Livewire.
Official Livewire documentation.
🌳 Single Root Element
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>
wire:key
is key!
🔑 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
✨ Performance
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.
🌿 Component nesting max 1 level
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>
🔮 Use Route Model Binding
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" />
🕵️ Do not pass sensitive data to components
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.
📦 Using computed properties
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();
}
📈 Use Lazy Loading
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 />
🔗 Entangle
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>
Alternative: x-modelable
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 Tips
🔝 ScrollTop After Validation
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
I'm a full-stack web developer working with the TALL stack.