Repository: livewire/livewire (php)

Issue:
x-on:click called twice after livewire call under certain circumstances
**Describe the bug**
`x-on:click` event called twice after livewire communication under certain circumstances.

**To Reproduce**
Steps to reproduce the behavior:
- laravel v7.7.2
- livewire v1.0.9
- alpinejs v2.2.1

### Test.php
```php
class Test extends Component
{
    public $name;

    public $items;

    public function mount()
    {
        $this->items = [1, 2];
    }

    public function render()
    {
        return view('livewire.test');
    }
}
```

### test.blade.php
```html
<div>
    <input type="text" wire:model="name"/>

    @foreach($items as $item)
        <div x-data="{ open:false }" wire:key="{{ $item }}" x-bind:key="{{ $item }}">
            <div> <!-- If I remove this div, it works fine. -->
                <button x-on:click="console.log('clicked');">
                    click
                </button>
            </div>
        </div>
    @endforeach
</div>
```

1. click the button and see the browser console. it works nomally.
2. put some characters in the input box. It will make a livewire call.
3. click the button again and see the browser console. it will be called twice.

**Expected behavior**
It should be called once.

**Screenshots**
![livewire](https://user-images.githubusercontent.com/978944/77432415-607cf700-6e21-11ea-83af-508ae27b7248.gif)


**Desktop (please complete the following information):**
 - chrome, safari, firefox

**Additional context**
I have originally made a replies component. It has several buttons like edit and delete in every replies. When I click delete button, it shows a delete modal. If I click 'delete' button, it calls livewire action using javascript. But If I make some livewire actions such as deleting or editing, delete action runs twice. The code above is the simplified version of that.

Pull request:
Fix action doubling - closes #763
Reported in #763 and continued from PR in #1995. Sometimes Alpine event listeners are registered twice when inside a blade conditional. 

This PR is still missing one change, but it's dependent on https://github.com/alpinejs/alpine/pull/1058. Marking as a draft until the fate of the PR on the Alpine repo is determined. There will be no other changes required for Livewire since the main fix is in Alpine. 

The change is only in the tests, and it'll just be bumping the Alpine CDN version in the testing `app.blade.php` layout.

Thanks to @joshhanley for writing the original test for this! I've added an extra test case to ensure that a nested alpine component doesn't have event listener problems too. 

Patch:
diff --git a/dist/manifest.json b/dist/manifest.json
index 7e1c0340..48df1978 100644
--- a/dist/manifest.json
+++ b/dist/manifest.json
@@ -1,1 +1,1 @@
-{"/livewire.js":"/livewire.js?id=d9e06c155e467adb5de2"}
\ No newline at end of file
+{"/livewire.js":"/livewire.js?id=25f025805c3c370f7e87"}
\ No newline at end of file
diff --git a/src/HydrationMiddleware/HydratePublicProperties.php b/src/HydrationMiddleware/HydratePublicProperties.php
index 634bd7a8..be7eeddc 100644
--- a/src/HydrationMiddleware/HydratePublicProperties.php
+++ b/src/HydrationMiddleware/HydratePublicProperties.php
@@ -120,6 +120,12 @@ class HydratePublicProperties implements HydrationMiddleware
             new ModelIdentifier($serialized['class'], $serialized['id'], $serialized['relations'], $serialized['connection'])
         );
 
+        /*
+         * Use `loadMissing` here incase loading collection relations gets fixed in Laravel framework,
+         * in which case we don't want to load relations again.
+         */
+        $models->loadMissing($serialized['relations']);
+
         $dirtyModelData = $request->memo['data'][$property];
 
         foreach ($idsWithNullsIntersparsed as $index => $id) {


Test patch:
diff --git a/tests/Browser/Alpine/ClickComponent.php b/tests/Browser/Alpine/ClickComponent.php
new file mode 100644
index 00000000..e95110bb
--- /dev/null
+++ b/tests/Browser/Alpine/ClickComponent.php
@@ -0,0 +1,35 @@
+<?php
+
+namespace Tests\Browser\Alpine;
+
+use Illuminate\Support\Facades\View;
+use Livewire\Component as BaseComponent;
+
+class ClickComponent extends BaseComponent
+{
+    public $show = false;
+
+    public function render()
+    {
+        return
+<<<'HTML'
+<div>
+    <div x-data="{ clicks: [] }">
+        <button dusk="show" wire:click="$set('show', true)">Toggle Options</button>
+
+        <div>
+            @if ($show)
+                <button dusk="click" x-on:click="clicks.push('Clicked')">Click</button>
+                <button dusk="componentClick" x-data="{ componentClicks: [] }" x-on:click="componentClicks.push('Clicked')">
+                    Component Click
+                    <div>Number of component clicks: <span dusk="alpineComponentClicksFired" x-text="componentClicks.length"></span></div>
+                </button>
+            @endif
+        </div>
+
+        <div>Number of clicks: <span dusk="alpineClicksFired" x-text="clicks.length"></span></div>
+    </div>
+</div>
+HTML;
+    }
+}
diff --git a/tests/Browser/Alpine/Test.php b/tests/Browser/Alpine/Test.php
index fe0b53b3..7dcc645b 100644
--- a/tests/Browser/Alpine/Test.php
+++ b/tests/Browser/Alpine/Test.php
@@ -141,4 +141,25 @@ class Test extends TestCase
             ;
         });
     }
+
+    public function test_alpine_registers_click_handlers_properly_on_livewire_change()
+    {
+        $this->browse(function ($browser) {
+            Livewire::visit($browser, ClickComponent::class)
+                ->waitForLivewire()->click('@show')
+                ->click('@click')
+                ->assertSeeIn('@alpineClicksFired', 1)
+                ->click('@click')
+                ->assertSeeIn('@alpineClicksFired', 2)
+                ->click('@click')
+                ->assertSeeIn('@alpineClicksFired', 3)
+                ->click('@componentClick')
+                ->assertSeeIn('@alpineComponentClicksFired', 1)
+                ->click('@componentClick')
+                ->assertSeeIn('@alpineComponentClicksFired', 2)
+                ->click('@componentClick')
+                ->assertSeeIn('@alpineComponentClicksFired', 3)
+            ;
+        });
+    }
 }
diff --git a/tests/Browser/DataBinding/EagerLoading/Component.php b/tests/Browser/DataBinding/EagerLoading/Component.php
new file mode 100644
index 00000000..d640b360
--- /dev/null
+++ b/tests/Browser/DataBinding/EagerLoading/Component.php
@@ -0,0 +1,49 @@
+<?php
+
+namespace Tests\Browser\DataBinding\EagerLoading;
+
+use Livewire\Component as BaseComponent;
+
+class Component extends BaseComponent
+{
+    public $posts;
+
+    public $comments;
+
+    public function mount()
+    {
+        $this->posts = Post::with('comments')->get();
+        $this->comments = Comment::all();
+    }
+
+    public function postsCommentsRelationIsLoaded()
+    {
+        return $this->posts->every(function ($post) {
+            return $post->relationLoaded('comments');
+        });
+    }
+
+    public function commentsHaveNoRelations()
+    {
+        return $this->comments->every(function ($comments) {
+            return $comments->getRelations() === [];
+        });
+    }
+
+    public function render()
+    {
+        return <<<'HTML'
+<div>
+    <div dusk="posts-comments-relation-loaded">
+        {{ $this->postsCommentsRelationIsLoaded() ? 'true' : 'false' }}
+    </div>
+
+    <div dusk="comments-has-no-relations">
+        {{ $this->commentsHaveNoRelations() ? 'true' : 'false' }}
+    </div>
+
+    <button dusk="refresh-server" type="button" wire:click="$refresh">Refresh Server</button>
+</div>
+HTML;
+    }
+}
diff --git a/tests/Browser/DataBinding/EagerLoading/Test.php b/tests/Browser/DataBinding/EagerLoading/Test.php
new file mode 100644
index 00000000..5b1e9090
--- /dev/null
+++ b/tests/Browser/DataBinding/EagerLoading/Test.php
@@ -0,0 +1,70 @@
+<?php
+
+namespace Tests\Browser\DataBinding\EagerLoading;
+
+use Illuminate\Database\Eloquent\Model;
+use Laravel\Dusk\Browser;
+use Livewire\Livewire;
+use Sushi\Sushi;
+use Tests\Browser\TestCase;
+
+class Test extends TestCase
+{
+    /** @test */
+    public function it_restores_eloquent_colletion_eager_loaded_relations_on_hydrate()
+    {
+        $this->browse(function (Browser $browser) {
+            Livewire::visit($browser, Component::class)
+                    ->assertSeeIn('@posts-comments-relation-loaded', 'true')
+                    ->waitForLivewire()->click('@refresh-server')
+                    ->assertSeeIn('@posts-comments-relation-loaded', 'true')
+                    ;
+        });
+    }
+
+    /** @test */
+    public function models_without_eager_loaded_relations_are_not_affected()
+    {
+        $this->browse(function (Browser $browser) {
+            Livewire::visit($browser, Component::class)
+                    ->assertSeeIn('@comments-has-no-relations', 'true')
+                    ->waitForLivewire()->click('@refresh-server')
+                    ->assertSeeIn('@comments-has-no-relations', 'true')
+                    ;
+        });
+    }
+}
+
+class Post extends Model
+{
+    use Sushi;
+
+    protected $rows = [
+        ['id' => 1, 'name' => 'post1'],
+        ['id' => 2, 'name' => 'post2'],
+    ];
+
+    public function comments()
+    {
+        return $this->hasMany(Comment::class);
+    }
+}
+
+class Comment extends Model
+{
+    use Sushi;
+
+    protected $rows = [
+        ['comment' => 'comment1', 'post_id' => 1],
+        ['comment' => 'comment2', 'post_id' => 1],
+        ['comment' => 'comment3', 'post_id' => 1],
+        ['comment' => 'comment4', 'post_id' => 1],
+        ['comment' => 'comment5', 'post_id' => 2],
+        ['comment' => 'comment6', 'post_id' => 2],
+    ];
+
+    public function post()
+    {
+        return $this->belongsTo(Post::class);
+    }
+}
diff --git a/tests/Browser/views/layouts/app.blade.php b/tests/Browser/views/layouts/app.blade.php
index f0ec1ec0..ca072bff 100644
--- a/tests/Browser/views/layouts/app.blade.php
+++ b/tests/Browser/views/layouts/app.blade.php
@@ -1,7 +1,7 @@
 <html>
 <head>
     <meta name="csrf-token" content="{{ csrf_token() }}">
-    <script src="https://cdn.jsdelivr.net/gh/alpinejs/alpine@v2.7.3/dist/alpine.min.js" defer></script>
+    <script src="https://cdn.jsdelivr.net/gh/alpinejs/alpine@v2.8.1/dist/alpine.min.js" defer></script>
     <!-- <script src="http://alpine.test/dist/alpine.js" defer></script> -->
     @livewireStyles
 </head>