Appearance
Core Concepts - Livewire 3
Livewire is a framework for Laravel that enables dynamic interfaces without writing JavaScript. It allows you to build interactive components with PHP, making it easier to create complex UIs with minimal client-side code.
TIP
This page is an overview of the core concepts of Livewire, offering a quick reference to its key features and functionalities. For more in-depth information, we've included the relevant documentation links.
Livewire also offers a wealth of features beyond what is covered on this page. For a comprehensive guide to everything Livewire has to offer, refer to the official documentation.
Request flow
Without Livewire (or another front-end solution), Laravel reloads the entire page for every interaction, which can slow down the user experience. Livewire improves this by handling interactions dynamically—only the necessary parts of the page are updated, with AJAX requests managed behind the scenes. No custom JavaScript is needed, allowing you to build responsive, modern applications using just Blade and Laravel.
Using just Laravel
Without Livewire or Inertia, each user interaction (like using a filter) triggers a full page refresh, leading to slower performance.
Using Laravel with Livewire
With Livewire, only the first page load is a full refresh. After that, interactions update parts of the page dynamically, improving performance.
Livewire vs Alpine.js
You might wonder, don't Livewire and Alpine.js do the same thing? Not quite. Both Livewire and Alpine.js help make your application dynamic, but they operate totally differently. They are complementary tools, built by the same developer (Caleb Porzio), that are meant to be used together to create a seamless user experience.
- Livewire processes interactions on the server side. Every user action triggers a request to the server, which returns updated data and re-renders part of the page. While this keeps your JavaScript minimal, relying on the server for every interaction can add latency and increase server load for high-traffic apps. This can slow down the user experience, especially for small, frequent interactions.
- Alpine.js handles interactions directly in the browser using JavaScript. This allows for instant feedback without communicating with the server for every action. It's more efficient for smaller UI updates and is a lightweight way to add interactivity without needing a server round-trip.
In short, Alpine.js is for simpler, client-side interactions, while Livewire manages more advanced, server-side interactions within your Laravel app.
When to use:
- Use Livewire when you need to interact with the backend, like submitting forms or fetching data.
- Use Alpine.js for simple interactions that don't require server-side logic, such as toggling visibility or handling button clicks.
Create & render a component
shell
# Create a Livewire component called ProductCatalog
$ php artisan make:livewire ProductCatalog
# OR
$ php artisan livewire:make ProductCatalog
# Creates a component class which contains the logic (in app/Livewire)
# Creates a blade view (in resources/views/livewire)
# Create a component in a subdirectory
$ php artisan make:livewire Shop\\ProductCatalog
# OR
$ php artisan make:livewire shop.product-catalog
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
Once a Livewire component is made you can render it in other blade views:
blade
{{-- Render a component --}}
<livewire:product-catalog />
{{-- OR --}}
@livewire('product-catalog')
1
2
3
4
2
3
4
Or render it in a route when used as a full-page component:
php
Route::get('catalog', ProductCatalog::class)->name('catalog');
1
Component Class
Each Livewire component has an associated class which is found in the app/Livewire
directory. This class handles logic for that specific component and can act as a controller (in our course this is always the case).
By default it has a render method that returns the associated blade view.
Below is an example of a Livewire (full-page) component class which demonstrates the commonly used features.
php
// Import anything you use
use Livewire\Component;
use Livewire\WithPagination;
use App\Models\Product;
use Livewire\Attributes\Layout;
class ProductCatalog extends Component
{
// For full-page components Livewire, by default, will look for a layout at resources/views/components/layouts/app.blade.php
// Set a different layout file using Livewire\Attributes\Layout, second argument is to fill the slots directly
#[Layout('layouts.app-layout', [
'title' => 'Product catalog',
'description' => 'Take a look at our product catalog'
])]
// You can also set a global layout in the config at 'config/livewire.php', replacing the default
// Make use of Livewire pagination
use WithPagination;
// Create public properties
public string $search = '';
public $selectedProduct = null;
// Make a public function, accessible in the component view
public function selectProduct($productId)
{
$this->selectedProduct = Product::find($productId);
}
// You can add a mount function which only gets called once when component is loaded
public function mount()
{
$this->selectedProduct = Product::first();
}
// render function is called on first render & everytime a public property changes
public function render() {
// Fetch products and use the pagination feature
$products = Product::where('name', 'like', '%' . $this->search . '%')->paginate(10);
// Return the related blade view and pass data to it (associative array)
return view('livewire.product-catalog', [
'products' => $products,
]);
// You can use the compact function to simplify (when names are equal)
return view('livewire.product-catalog', compact('products');
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
Directives
In the blade view of a Livewire component (found in the resources/views/livewire
directory) you can make use of some handy Livewire HTML directives. Below are the directives discussed in the course, for more take a look at the Livewire documentation.
wire:click
The wire:click directive allows you to define actions to execute when an element, such as a button, is clicked.
blade
{{-- Call an action on click (with parameters if needed --}}
<a href="#" wire:click="selectProduct({{ $product->id }})">{{ $product->name }}</a>
{{-- Add .prevent to prevent default browser behaviour when needed --}}
<a href="#" wire:click.prevent="selectProduct({{ $product->id }})">{{ $product->name }}</a>
1
2
3
4
5
2
3
4
5
wire:submit
The wire:submit
directive links a form to a Livewire action for submission.
blade
<form wire:submit="submitContactForm">
{{-- Form fields here, use wire:model (see below) for the inputs --}}
<button type="submit">Submit</button>
</form>
1
2
3
4
2
3
4
REMARK
- For
wire:submit
Livewire automatically callsevent.preventDefault()
as this is (almost) always the desired behaviour. Use the.prevent
modifier on other event listeners where needed. - Livewire also disables form submit buttons and marks all form inputs as
readonly
while submitting so users cannot submit the same form again.
wire:model
The wire:model
directive creates two-way data binding for input fields, ensuring that changes in an input field are synchronized with the associated property in the Livewire component.
blade
<input type="text" wire:model="search" placeholder="Search products">
1
By default, Livewire sends network requests when an action, such as wire:click or wire:submit, is performed. However, it does not send a network request when a wire:model input is updated.
For this Livewire provides modifiers to give you control over when a network request is send. Below is a list of all currently available modifiers.
Modifier | Description |
---|---|
.live | Sends updates as a user types with a 150ms delay. |
.live.debounce.[?]ms | Debounce the sending of updates by the specified millisecond delay. |
.live.throttle.[?]ms | Throttle network request updates by the specified millisecond interval. |
.blur | Only send updates on the blur event. |
.change | Only send updates on the change event. |
.lazy | An alias for .change . |
.number | Cast the text value of an input to an integer on the server. |
.fill | Use the initial value provided by a "value" HTML attribute on page-load. |
Example: Adding the .live.debounce.500ms
modifier makes sure a network request is send each time a user has changed the input with a delay of 500 milliseconds.
blade
<input type="text" wire:model.live.debounce.500ms="search" placeholder="Search products">
1
wire:loading
The wire:loading
directive is used to conditionally display loading indicators.
blade
{{-- Only shown during Livewire actions --}}
<div wire:loading> Loading... </div>
{{-- Show content when not doing any Livewire actions --}}
<div wire:loading.remove> Not doing any actions right now </div>
{{-- Use a class during Livewire actions --}}
<button wire:loading.class="opacity-50"> Save </button>
{{-- Remove a class during Livewire actions --}}
<button class="bg-blue-500" wire:loading.class.remove="bg-blue-500"> Save </button>
{{-- Add an attribute during Livewire actions --}}
<button type="button" wire:click="remove" wire:loading.attr="disabled"> Remove </button>
{{-- Target a specific action OR property change --}}
<div wire:loading wire:target="remove"> Removing product... </div>
<div wire:loading wire:target="search"> Search input has been changed </div>
{{-- Target a specific action with a specific parameter --}}
<div wire:loading wire:target="remove({{ $product->id }})"> Removing product... </div>
{{-- Customize the display property on show (default = none on hide & inline-block on show) --}}
<div wire:loading.inline-flex>...</div>
<div wire:loading.inline>...</div>
<div wire:loading.block>...</div>
<div wire:loading.table>...</div>
<div wire:loading.flex>...</div>
<div wire:loading.grid>...</div>
{{-- Delay a loading indicator --}}
<div wire:loading.delay.shortest>...</div> <!-- 50ms -->
<div wire:loading.delay.shorter>...</div> <!-- 100ms -->
<div wire:loading.delay.short>...</div> <!-- 150ms -->
<div wire:loading.delay>...</div> <!-- 200ms -->
<div wire:loading.delay.long>...</div> <!-- 300ms -->
<div wire:loading.delay.longer>...</div> <!-- 500ms -->
<div wire:loading.delay.longest>...</div> <!-- 1000ms -->
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
wire:key
wire:key
is an attribute used within Blade templates in Livewire to help track and efficiently update elements within loops. It's essential to ensure that each wire:key
value is unique within the scope of the loop it's used in to prevent rendering issues.
blade
@foreach($products as $product)
<div wire:key="{{ $product->id }}">
{{ $product->name }}
</div>
@endforeach
1
2
3
4
5
2
3
4
5
Example component view
This example view (for a product catalog component) uses the class discussed previously in Livewire - Component Class. It uses most of the Livewire functionalities we use in the course.
blade
<div>
{{-- Use wire:model for two-way data binding --}}
<input type="text" wire:model.live="search" placeholder="Search products">
{{-- Use wire:loading to show content during Livewire action --}}
<div wire:loading>
Loading updated ...
</div>
<div>
<h2>Selected Product:</h2>
<p>{{ $selectedProduct->name }}</p>
</div>
<ul>
@foreach($products as $product)
{{-- Use wire:key in loops so Livewire can keep track --}}
<li wire:key="product-{{ $product->id }}">
{{-- Use wire:click to call a function in the class on click --}}
<a href="#" wire:click.prevent="selectProduct({{ $product->id }})">
{{ $product->name }}
</a>
</li>
@endforeach
</ul>
{{-- Render pagination links --}}
{{ $products->links() }}
{{-- To customize the pagination links -> php artisan livewire:publish --pagination --}}
{{-- You can then find the files in resources/views/vendor/livewire --}}
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
$wire object
The $wire object is an automatically injected 'magic' JavaScript object that is available within the scope of your Livewire component's view. It acts as a bridge between your Livewire component's backend (PHP) and frontend (JavaScript/HTML).
Alpine is our frontend Javascript framework and thus also has access to this object which is the main use case in our course. In fact, under the hood, every Livewire component is also an Alpine component.
Documentation propertiesDocumentation actions
blade
{-- Access a property from our class --}
$wire.propertyName
{-- Access a function from our class --}
$wire.functionName()
{-- Set a certain value for a property --}
$wire.set('propertyName', 'newValue')
{-- Example - Keeping client state with Alpine and passing it to the PHP function in our Livewire class --}
<div x-data="{ todo: '' }">
<input type="text" x-model="todo">
<button x-on:click="$wire.addTodo(todo)">Add Todo</button>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Validation
Livewire's #[Validate]
attribute simplifies the process of implementing validation rules directly in component methods. Livewire will automatically run the properties validation rules before each update.
You can find all the available validation rules in the Laravel validation documentation
php
use Livewire\Attributes\Validate;
// ...
#[Validate('required|min:3')]
public $newName = '';
1
2
3
4
5
6
2
3
4
5
6
You can display an error when validation fails by using the @error
directives:
blade
@error('newName') <span class="error">{{ $message }}</span> @enderror
1
You can pass a custom attribute name into the validation message that is shown by using the as
parameter:
php
#[Validate('required|min:3', as: 'name of the person')]
public $newName = '';
1
2
2
Below is an example using these validation features on a property that holds multiple values in an associative array:
php
#[Validate([
'formData.username' => 'required|unique:users,username',
'formData.age' => 'required|numeric'
], as: [
'formData.username' => 'Username',
'formData.age' => 'Age'
])]
public $formData = ['username' => null, 'age' => null];
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
For unique
validation rules for create
and update
you have to use the rules()
method instead of the #[Validate]
attribute.
php
// #[Validate('required|size:36|unique:records,mb_id,id', as: 'MusicBrainz ID')] // DOES NOT WORK
public $mb_id = null;
// special validation rule for mb_id (unique:records,mb_id,id) for insert and update!
public function rules()
{
return [
'mb_id' => "required|size:36|unique:records,mb_id,{$this->id}",
];
}
// $validationAttributes is used to replace the attribute name in the error message
protected $validationAttributes = [
'mb_id' => 'MusicBrainz ID',
];
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15