We’re running into an odd problem with AlpineJS and Phoenix LiveView, whereby Alpine reacts to the previous value of an assign when it changes. For example, with the following LEEx (lifted verbatim from Patrick Thompson’s excellent blog post:
<div id="counter" x-data="{count: <%= count %>}">
<h1>The assigns count is: <span><%= @count %></span></h1>
<h1>The alpine count is: <span x-text="count"></span></h1>
</div>
With Alpine v2, both the assigns count and Alpine count are kept in sync whenever the assign is updated however with Alpine v3, the Alpine counter is always one update behind the assign.
I’ve illustrated the whole problem in this Loom video.
Has anyone else seen (or can reproduce) this issue? The full LiveView is as follows:
defmodule MyApp.AlpineLive do
use Phoenix.LiveView
@impl Phoenix.LiveView
def render(assigns) do
~L"""
<div id="counter" class="m-8" x-data="{count: <%= @count %>}">
<h1>The assigns count is: <span><%= @count %></span></h1>
<h1>The alpine count is: <span x-text="count"></span></h1>
<button class="button button-primary" phx-click="decrement"> Decrement </button>
<button class="button button-primary" phx-click="increment"> Increment </button>
</div>
"""
end
@impl Phoenix.LiveView
def mount(_, _, socket) do
count = if connected?(socket), do: 5, else: 0
{:ok, assign(socket, count: count)}
end
@impl Phoenix.LiveView
def handle_event("increment", _, socket) do
{:noreply, update(socket, :count, &(&1 + 1))}
end
def handle_event("decrement", _, socket) do
{:noreply, update(socket, :count, &(&1 - 1))}
end
end
FWIW our app.js
integrates AlpineJS with the following option on the LiveSocket
(which supports both Alpine v2 and v3):
dom: {
onBeforeElUpdated(from, to) {
if (!window.Alpine) return;
if (from.nodeType !== 1) return;
// AlpineJS v2
if (from.__x) window.Alpine.clone(from.__x, to);
// AlpineJS v3
if (from._x_dataStack) window.Alpine.clone(from, to);
},
},