Big New Livewire Release: v2.6.0

News

September 14th, 2021

laravel-livewire.png

Livewire is getting lots of quality-of-life improvements in the latest release (v2.6). This is the biggest Livewire release since version 2.0.

This release is extra special because the majority of these updates were completely PR’d and driven by the community. Special shout out to @joshhanley for developing basically half of them and helping out with most of them.

Here’s the full release notes with links to every feature, fix, and PR that was made for this release: https://github.com/livewire/livewire/releases/tag/v2.6.0

Let’s jump in. Here’s what’s up:

New boot lifecycle hook

Pull Request: #3689

As of 2.6, there is a new boot() lifecycle method available for your Livewire components. This method will run before any other lifecycle hook AND will run on every single request, both initial and subsequent component requests.

If the words “initial and subsequent requests” in the context of Livewire are fuzzy for you, give this post a quick read.

1class YourComponent extends Livewire\Component
2{
3 public function boot()
4 {
5 //
6 }
7}

You can also use this lifecycle hook within traits by adding the class name as a suffix to avoid inheritance collisions. For example:

1class YourComponent extends Livewire\Component
2{
3 use YourTrait;
4 
5 public function boot()
6 {
7 //
8 }
9}
1trait YourTrait
2{
3 public function bootYourTrait()
4 {
5 //
6 }
7}

Because mount() currently runs only on the initial request, and hydrate() only runs on subsequent ones, there hasn’t been a good way to run code on EVERY request. Many users achieved this by overriding the constructor which is highly discouraged. Now we have boot()!

Deep model data binding

Pull Request: #3409

Livewire has supported binding directly to Eloquent model attributes for a long time, however, v2.6 takes it to a whole new level.

Let’s look at an example:

If you had an Eloquent model set to a Livewire property like public $post, you could bind a text input to one of its attributes like so:

1<input type="text" wire:model="post.title">

However, this mechanism wouldn’t work for binding to attributes of a nested model relationship. After this update, given an Eloquent model property on a Livewire component like $user, you can now bind directly to deeply nested relationships like so:

1<input type="text" wire:model="user.posts.0.title">

Here’s a more fleshed out example of a page for a user to edit all the titles of their posts:

1class EditUsersPosts extends Component
2{
3 public User $user;
4 
5 protected $rules = [
6 'user.posts.*.title'
7 ];
8 
9 public function save()
10 {
11 $this->validate();
12 
13 $this->user->posts->each->save();
14 }
15}
1<div>
2 @foreach ($user->posts as $i => $post)
3 <input type="text" wire:model="user.posts.{{ $i }}.title" />
4 @endforeach
5 
6 <button wire:click="save">Save</button>
7</div>

With this setup, you can directly bind the value of an input element to the title of a post on the $user property.

This saves on loads of boilerplate code and allows for cleaner Livewire model patterns to emerge in the future.

Wireable objects

Pull Request: #3362

There is a new Wireable interface that if present, allows any object to be stored and persisted as a public property on a Livewire component.

Here’s the trait:

1<?php
2 
3namespace Livewire;
4 
5interface Wireable
6{
7 public function toLivewire();
8 
9 public static function fromLivewire($value);
10}

This is extremely helpful for using things like custom DTOs (Data Transfer Object) with your Livewire components.

For example, let’s say we have a custom object in our app called Settings. Rather than just store settings data as a plain array on our Livewire component, we can attach associated behavior to this data with a convenient wrapper object or DTO like Settings:

1class Settings implements Livewire\Wireable
2{
3 public $items = [];
4 
5 public function __construct($items)
6 {
7 $this->items = $items;
8 }
9 
10 ...
11 
12 public function toLivewire()
13 {
14 return $this->items;
15 }
16 
17 public static function fromLivewire($value)
18 {
19 return new static($value);
20 }
21}

Now you can freely use this object as a public property of your component like so:

1class SettingsComponent extends Livewire\Component
2{
3 public Settings $settings;
4 
5 public function mount() {
6 $this->settings = new Settings([
7 'foo' => 'bar',
8 ]);
9 }
10 
11 public function changeSetting()
12 {
13 $this->settings->foo = 'baz';
14 }
15}

And as you can see, changes to the component are persisted between requests because, with Wireable, Livewire knows how to “dehydrate” and “re-hydrate” this property on your component.

If words like “hydrate” or “dehydrate” in the context of Livewire are fuzzy for you, give this post a quick read.

Array targeted loading indicators

Pull Request: #3530

In cases where you want a loading indicator to be scoped to ALL changes in an array, not just specific, singular, items in the array, you can now reference the array property itself.

For example, If you have two different Livewire properties, one is an array and the other is not, you may want to show a loading indicator for any changes made to the array and not to the other one.

1class SomeComponent extends Livewire\Component
2{
3 public $author = '';
4 
5 public $post = [
6 'title' => '',
7 'content' => '',
8 ];
9}
1<div>
2 <input type="text" wire:model="author">
3 
4 <input type="text" wire:model="post.title" />
5 
6 <textarea wire:model="post.content"></textarea>
7 
8 <div wire:loading wire:target="post">Loading...</div>
9</div>

In the above component, the loading indicator will only show when updates are being made to the post property, not the author property.

This is a better alternative to having to list out every single target like so:

1<div wire:loading wire:target="post.title">Loading...</div>
2<div wire:loading wire:target="post.content">Loading...</div>

Warn when multiple root elements are present

Pull Request: #3289

Livewire components can only have ONE root element. If two are present, Livewire fails silently causing lots of confusion for newcomers.

Now, Livewire will detect a second root element for a component and warn the user in the console.

Parsing HTML to detect multiple root elements in the backend is very hard and problematic. This is why we are not throwing a backend error, but instead throwing a warning on the front-end.

To make this possible, Livewire now adds a small HTML comment at the bottom of every component’s HTML so that the front-end can easily track the scope of any given component and detect more than one root element.

Private methods and props available via $this

Pull Request: #3640

Livewire currently exposes a $this object inside your component’s Blade view. This way you have easy access to both properties and methods on your component object from inside your views.

Currently, you can only access PUBLIC properties and methods through $this. Now, both protected and private methods and properties will be available on $this, just as if it was an actual $this instance.

Custom wire:loading.delay durations

Pull Request: #3529

A lesser-known feature of Livewire’s loading system is the .delay modifier.

When appended to wire:loading, .delay prevents the loading indicator from showing if the request is completed within 200ms.

This way you don’t get a loading indicator “blip” that can be jarring to users.

Although useful in its current state, often people want to configure the exact time delay of .delay.

You’d think it’s as simple as supporting time suffixes like other APIs in Livewire and Alpine do. For example, the logical API for this feature would be something like: wire:loading.delay.500ms

However, this is an extremely hard API to achieve in this case because of CSS selector constraints. Unlike other wire:? directives, wire:loading has secret special behavior. If you dig through the styles that Livewire adds to your page, you will notice something like this:

1[wire\:loading], ... {
2 display: none;
3}

This style exists because, without it, all loading indicators would show up for a split second on the page before Livewire’s JavaScript initializes. This is often referred to as “page jank”.

To avoid this, Livewire adds this CSS style that hides any wire:loading element until Livewire initializes.

The problem is that it is currently impossible in CSS to make a selector with a wildcard in the attribute. Ideally, we’d be able to do something like this:
[wire\:loading.*]. But unfortunately, that isn’t the world we live in.

So instead, we are going with pre-defined values for different .delay modifiers. Here they are:

  • wire:loading.delay.shortest (50ms)
  • wire:loading.delay.shorter (100ms)
  • wire:loading.delay.short (150ms)
  • wire:loading.delay (200ms)
  • wire:loading.delay.long (300ms)
  • wire:loading.delay.longer (500ms)
  • wire:loading.delay.longest (1000ms)

Multiple paginator support

Pull Request: #3684

In Livewire, you can add the WithPagination trait to any component that uses Laravel’s pagination features and like magic, pagination will become SPA-like requiring no page re-loads.

However, the current system has a few flaws which have been addressed in v2.6. Most notably of which is support for multiple paginators.

Because Livewire hardcodes the $page property inside the WithPagination trait, there is no way to have two different paginators on the same page because each will be competing for the same property name in the query string of the URL bar.

Here’s an example of two different components that might exist on the same page. By giving the second one (the comments one) a name, Livewire will pick it up and handle everything accordingly.

1class ShowPosts extends Livewire\Component
2{
3 public function render()
4 {
5 return view('livewire.show-posts', [
6 'posts' => Post::paginate(10),
7 ]);
8 }
9}
1class ListPostComments extends Livewire\Component
2{
3 public Post $post;
4 
5 public function render()
6 {
7 return view('livewire.show-posts', [
8 'posts' => $post->comments()->paginate(10, ['*'], 'commentsPage'),
9 ]);
10 }
11}

Now in the query string, both paginators will be represented like so:

?page=2&commentsPage=3

Support for redirect()->with()

Pull Request: #3572

Livewire supports redirecting from any action called within your component using the standard APIs you’re used to using from a controller.

However, because Livewire hijacks Laravel’s redirector with its own, it hasn’t supported extra niceties like ->with() which is used to attach flash data along with the redirect.

No more. Livewire now allows ->with() to be appended to your redirect().

See it in action:

1class EditPost extends Livewire\Component
2{
3 ...
4 
5 public function save()
6 {
7 ...
8 
9 return redirect()
10 ->to('/posts')
11 ->with('success', 'Post updated successfully!');
12 }
13}

Cleaner query strings

Pull Request: #3524

Livewire’s protected $queryString feature allows you to easily and declaratively track data inside the query string of your app’s URL.

However, currently, when you track an array property in your query string, it looks yucky. For example, here’s an array stored in the query string currently:

1class ShowItems extends Component
2{
3 public $items = [
4 'foo' => 'bar',
5 'bob' => 'lob',
6 ];
7 
8 protected $queryString = ['items'];

Before this update, the query string in the URL bar would look like this:

?items%5Bfoo%5D=bar&items%5Bbob%5D=lob

Here’s what that snippet will look like now after v2.6:

?items[foo]=bar&items[bob]=lob

Much cleaner.

Browser back-button cache config

Pull Request: #2747

Here’s the problem: Google Chrome caches the initial HTML received from the server when you load a page. When a user hits the back button after leaving that page, Chrome immediately loads the page based on its initial HTML without making a new network request.

This behavior is problematic for Livewire (and really any non-trivial front-end setup) because if, for example, a user starts making changes on a page, when the user hits the back button, they will see whatever the state of the initial component was, not the most up-to-date state.

Other browsers like Safari and Firefox don’t deal with this issue at all as they use a much more sophisticated cache system called “bfcache” (back-forward cache). Bfcache systems freeze the entire state of a page before a user leaves it, including the JavaScript runtime. This means when a user hits the back button in one of these browsers, the perfect up-to-date state is shown to them.

Chrome has its bfcache feature behind a feature flag but it still doesn’t ship enabled with browsers.

Because Chrome is so popular and until this happens, Livewire has decided to disabled the cache system entirely so that when a user hits the. back button, the browser will fetch a completely fresh page with completely fresh state.

This of course will be a slightly slower experience, but in our experience, 99% of users and use cases would rather have state in-sync than instantaneous back button visits.

This option is now configurable via livewire.back_button_cache if you disagree.

Blade component layout love

Pull Request: #3363

Livewire allows you to render components as entire pages by simply passing them into your route definition like so:

1Route::get('/...', YourLivewireComponent::class);

By default, Livewire looks for a Blade layout component called: x-layouts.app.

This is a sensible default for people using Laravel Jetstream or people who acknowledge this pattern as a general Laravel app convention.

However, when you configure your own layout there are a few gotchas that have been addressed:

  1. You can now use anonymous components as your layout component.
  2. You can pass an FQCN (full class name) to Livewire’s livewire.layout config instead of just the component name.
  3. The $attributes bag will now always be available to layout components.

Support union types

Pull Request: #3663

PHP 8.0 now introduced support for union types (the ability to declare more than one type in a type hint).

However, Livewire hasn’t supported this for its own method dependency injection system.

For example, with this addition you can now use a union type for a mount() method parameter:

1class ShowPostOrComment extends Livewire\Component
2{
3 public $item;
4 
5 public function mount(Post|Comment $item) {
6 $this->item = $item;
7 }
8}

This was a difficult problem to think through. There are lots of questions like: What is the expected behavior here? If a user doesn’t pass in a parameter to the component what should the type be? Should Livewire use the first type in the list? Should it just pass null? Should it error out?

We ended up going with "passing null".

If you use a union type in a method dependency and Livewire has to figure out which class to resolve out of the container, it will just pass you null because that question is impossible to answer.

In most cases with union types, you would be explicitly passing the parameter into Livewire and everything will work as expected now.

Support adding/removing public properties on deploy

Pull Request: #2977

This is one of those sneaky little problems that many people have experienced and isn’t /technically/ a bug (but totally is because it causes people errors in their apps).

Here’s the situation: If you have a property on a Livewire component called $posts and many of your users have a page open in their browser that uses this component, IF you remove this property from the component and re-deploy your app, all those users that haven’t refreshed their page will receive an error the next time they interact with that component.

The problem exists because Livewire is extremely strict about people tampering with Component data for security’s sake and when it sees a new or removed property it assumes it was a malicious action.

However, because we can assume that backend code changes are intentional, Livewire now supports the addition and subtraction of properties on a component at runtime.

I know I’ve personally encountered this problem after deploying an update to a Livewire app and getting new bug notifications for a day or two until everyone has refreshed their pages.

Support DateTimeImmutable and CarbonImmutable

Pull Request: #3419

Over time Livewire has added support for more and more PHP types to be stored as public properties on components.

Version 2.6 now includes support for public properties of the type DateTimeImmutable and CarbonImmutable.

There it is folks

Those are all the significant additions to the Livewire project available in version 2.6.0.

There are a few more features and many more bug fixes included in the release. You can view the full release notes here.

Thanks for caring,
Caleb

Filed in:

Caleb Porzio

Hi friends! I'm Caleb. I created Livewire and Alpine because I love building tools that make web development feel like magic. I also love to fly fish!