Skip to main content

What's new in Livewire 4

·2047 words·10 mins·

Caleb Porzio recently tagged the release of Livewire 4, the latest major version of the popular full-stack framework for Laravel that lets developers build dynamic interfaces with ease. We already covered some of the beta features in a previous article, but now that the stable version is out, let’s take a closer look at what are the main new features and improvements in Livewire 4. Remember that most applications built with Livewire 3 can be upgraded to Livewire 4 with minimal changes. The breaking changes are primarily configuration updates and method signature changes that only affect advanced usage. You can see the full upgrade guide in the official documentation.

Installationa and Configuration
#

If you run laravel new to create a new Laravel application using Laravel installer and select the Livewire starter kit, it will now install Livewire 4 by default. For existing applications, you can upgrade to Livewire 4 by running:

composer require livewire/livewire:^4.0

Then clear the cache:

php artisan optimize:clear

If you didn’t chose the Livewire starter kit when creating your Laravel application, after manually installing Livewire 4, you can setup the layout scaffolding by running. This is nessesary if you plan to use Livewire full page components:

php artisan livewire:layout

This creates a layout file at resources/views/layouts/app.blade.php.

Components
#

Single File Components (SFCs)
#

In Livewire 4, there are three main types of components. If you run php artisan make:livewire counter, it will create a component at resources/views/components/⚡counter.blade.php. Notice the new ⚡ prefix? As you can see only one file is created, the class file is no longer generated by default. This is Single File Components (SFCs), which combine the Blade template and the component class into a single file.

<?php

use Livewire\Component;

new class extends Component
{
    //
};
?>

<div>
    {{-- Let all your things have their places; let each part of your business have its time. - Benjamin Franklin --}}
</div>

The purpose of ⚡ emoji prefix is to make it visually clear that this is a Livewire component when browsing the codebase. Since the comresources/views/components directory is also used for Blade components, this helps to avoid confusion between the two types of components.

If you don’t like the emoji prefix, you can change it in the livewire.php configuration file. Run php artisan livewire:config to publish the configuration file if you haven’t done so already. In the make_command array, you can set the emoji key to true or false to enable or disable the emoji prefix.

Multi File Components (MFCs)
#

If you want to create different files for the php code and the Blade template, you can create a Multi File Component (MFC). Change the type key in the make_command array in the livewire.php configuration file to mfc. Now when you run php artisan make:livewire counter, it will create two files. Under resources/views/components/⚡counter, you will find counter.php and counter.blade.php. The counter.php file contains the component class, while the counter.blade.php file contains the Blade template.

Class Components
#

If you prefer the old way of creating Livewire components, you can still do so by changing the type key in the make_command array in the livewire.php configuration file to class. Now when you run php artisan make:livewire counter, it will create the component class at app/Livewire/Counter.php and the Blade template at resources/views/livewire/counter.blade.php, just like in Livewire 3.

Alternatively, you can specify the component type directly in the make command using the --sfc, --mfc, or --class options. This will ignore the default type set in the configuration file.

Converting Between Component Types
#

You can convert between component types using the php artisan livewire:convert command. For example, if you have a Single File Component called counter and you want to convert it to a Multi File Component, you can run:

php artisan livewire:convert counter --mfc

This will create a new directory at resources/views/components/⚡counter containing counter.php and counter.blade.php, and remove the original ⚡counter.blade.php file. This command works for converting between all three component types.

Islands
#

Let’s say you have a Livewire component that’s getting bigger and bigger. Inside the component, there might be little pieces that could make really expensive database queries or API calls. So the whole component becomes slow because of these few expensive parts. Before Livewire 4, you would have to break the big component into smaller nested components and manage the communication between them, which can be a hassle. What about you take a part of the component that does the expensive work and put it on an island?

You can use @island and @endisland directives to wrap the expensive part of your component in the blade file.

<?php // resources/views/components/⚡dashboard.blade.php
 
use Livewire\Attributes\Computed;
use Livewire\Component;
use App\Models\Revenue;
 
new class extends Component {

    #[Computed]
    public function revenue()
    {
        // Expensive calculation or query...
        return Revenue::yearToDate();
    }
};
?>
<div>
    @island
        <div>
            Revenue: {{ $this->revenue }}
 
            <button type="button" wire:click="$refresh">Refresh</button>
        </div>
    @endisland
 
    <div>
        <!-- Other content... -->
    </div>
</div>

Isalands are pure at compile time in Livewire, it extracts the islanded part of the component to a separate blade file, so if an action like $refresh is triggered inside the island, only the island part is re-rendered and sent to the client. When you are using islands, computed properties are importent because they run only when they are accessed. So other computed properties or methods in the main component won’t trigger the expensive calculation inside the island unless it’s needed.

There are many more to islands, like naming islands, passing data to islands, and nesting islands. You can read more about islands in the official documentation.

Lazy, Defer, and Bundle
#

Lazy
#

Lazy loading in Livewire is a incredibly powerful utility. Let’s check the below example:

<?php // resources/views/components/⚡revenue.blade.php
 
use Livewire\Component;
use App\Models\Transaction;
 
new class extends Component {
    public $amount;
 
    public function mount()
    {
        // Slow database query...
        $this->amount = Transaction::monthToDate()->sum('amount');
    }
};
?>
<div>
    Revenue this month: {{ $amount }}
</div>

Without lazy loading, the mount method runs as soon as the component is initialized, which can slow down the initial page load if the query is slow. To improve this, you can use the lazy parameter into the component.

<livewire:revenue lazy />

Above method is good if you want to make the component lazy each time it’s used. But what if you want to make it lazy every time without having to specify the lazy parameter? You can do this by adding the #[Lazy] attribute to the component class.

<?php // resources/views/components/⚡revenue.blade.php

use Livewire\Attributes\Lazy;
use Livewire\Component;
use App\Models\Transaction;

new #[Lazy] class extends Component {
    
    public $amount;

    public function mount()
    {
        // Slow database query...
        $this->amount = Transaction::monthToDate()->sum('amount');
    }
};
?>

Keep in mind that Livewre’s lazy system is only going to fetch what it needs to show on the screen. It waits until the component is visible in the viewport before it loads the data.

Placeholder
#

When using lazy loading, there might be a noticeable delay before the component loads, especially if the data fetching is slow. To improve user experience, you can provide a placeholder that will be displayed while the component is loading. You can do this by using the @placeholder directive in the Blade template.

<div>
    @placeholder
        <div>Loading revenue data...</div>
    @endplaceholder
    Revenue this month: {{ $amount }}
</div>

Defer
#

Defer loading is similar to lazy loading, but instead of waiting for the component to be visible in the viewport, it loads the component fully after the initial page load. You can ues defer anywhere you would use lazy. For example:

<livewire:revenue defer />

Bundle
#

Let’s say you have many Livewire components on a page, and each component is lazy loaded. This means that each component will make its own request to the server when it becomes visible in the viewport. This can lead to many requests being made to the server, which can be inefficient and slow down the page load time. You can set lazy.bundle on components to group multiple lazy components into a single request. For example:

<livewire:revenue lazy.bundle />

wire:intersect
#

The wire:intersect directive allows you to trigger actions when an element enters the viewport. This is useful for implementing infinite scrolling or loading more content as the user scrolls down the page. Here’s an example of how to use wire:intersect:

<?php // resources/views/components/⚡infinite-posts.blade.php

<div wire:intersect="loadMore">
    <!-- Content loads when scrolled into view -->
</div>
use Livewire\Component;

new class extends Component {
    public function loadMore()
    {
        // Load more content...
    }
};
?>

When the user scrolls down and the element with wire:intersect comes into view, the loadMore method will be called, allowing you to load additional content dynamically.

Events
#

You can specify events to listen for when using wire:intersect. For example, you can listen for the enter and leave events:

<!-- Runs when entering viewport (default) -->
<div wire:intersect="trackView">...</div>
 
<!-- Runs when entering viewport (explicit) -->
<div wire:intersect:enter="trackView">...</div>
 
<!-- Runs when leaving viewport -->
<div wire:intersect:leave="pauseVideo">...</div>

There are many configuration options available for wire:intersect, such as setting thresholds and root margins. You can read more about wire:intersect in the official documentation.

data-loading
#

In Livewire 3, you would use wire:loading to track loading states to show loading indicators. In Livewire 4, any element that triggers a network request automatically gets a data-loading attribute added to it while the request is in progress. You can use this attribute and style it however you want.

<button wire:click="save" class="data-loading:opacity-50">
    Save
</button>

You can use this to show elements during loading, style childrens, style parents and use complex selectors. Check the loading states documentation for more details: Loading States.

wire:sort
#

The wire:sort directive allows you to create sortable lists with drag-and-drop functionality. This is useful for reordering items in a list, such as tasks in a to-do list or products in a catalog. Here’s an example of how to use wire:sort:

To a list sortable, add the wire:sort directive to the parent element and specify the method to call when the order changes. Then, add the wire:sort.item directive to each item in the list and provide a unique identifier for each item.

<?php
 
use Livewire\Component;
use Livewire\Attributes\Computed;
 
new class extends Component {
    public Todo $todo;
 
    public function sortItem($item, $position)
    {
        $item = $this->todo->items()->findOrFail($item);
 
        // Update the item's position in the database and re-order other items...
    }
};
?>
<ul wire:sort="sortItem">
    @foreach ($todo->items as $item)
        <li wire:sort:item="{{ $item->id }}">
            {{ $item->title }}
        </li>
    @endforeach
</ul>

When you drag and drop an item to a new position in the list, the sortItem method will be called with the item’s identifier and its new position, allowing you to update the order in your database. You can read more about wire:sort in the official documentation.

Optimistic UI
#

If you use Livewire extensively, you might have noticed that sometimes there is a slight delay between when you perform an action and when the UI updates to reflect that action. This is because Livewire waits for the server to respond before updating the UI. It wouldn’t be a big issue if you have a fast internet connection and a responsive server, but in real-world scenarios, there can be network latency and server processing time that can lead to a noticeable delay.

wire:show
#

Instead of writing @if statements to conditionally show or hide elements based on component state, you can use the wire:show directive. This directive optimistically shows or hides elements immediately on the client side, without waiting for a server response.

<button wire:click="toggleDetails">
    Toggle Details
</button>

<div wire:show="showDetails">
    Here are the details...
</div>

In the above example, when the button is clicked, the showDetails property is toggled in the component. The wire:show directive immediately shows or hides the details div based on the value of showDetails, providing a snappier user experience.

When you use wire:show, the element that should be hidden may be briefly visible before being hidden, especially if the initial state is false. To avoid this, you can add the wire:cloak directive to the element, which will hide it until Livewire has fully initialized.

<div wire:show="showDetails" wire:cloak>
    Here are the details...
</div>

wire:text
#

The wire:text directive allows you to optimistically update text content on the client side without waiting for a server response. This is useful for providing immediate feedback to users when they perform actions that change text content.

<button wire:click="incrementCounter">
    Increment Counter
</button>
<div wire:text="counter">
    Counter: {{ $counter }}
</div>