simoncocking

simoncocking

Shadowed assigns with AlpineJS v3 and Phoenix LiveView 0.15

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);
    },
  },

Most Liked

simoncocking

simoncocking

So we’ve identified a workaround; the x-data behaviour in Alpine v3 appears to have changed, but it can be achieved as follows:

<div id="counter" x-data="{count: 0}" x-init="count = <%= @count %>">
  ...
</div>
polypush135

polypush135

I’ve tried this but with no luck

I have seen that if you call Alpine start again it re initializes and shows the correct data. My thought here is this is due to the way we are calling clone or what needs to happen to correctly re initialize

Any how heres my reproducible code.
https://github.com/MorphicPro/morphic_pro

Edit: so this looks like a possible solution Alpine.initTree()

Edit Edit:
Ok so I found it :tada:

        if (from._x_dataStack) {
          window.Alpine.clone(from, to);
          window.Alpine.initTree(to)
        }

and you don’t need to use x-init, Also heres something odd., look at what happens if you only use window.Alpine.initTree(to) But after inspecting the _x_dataStack array I’m not sure if that’s the correct way to do this.
Also this Expose the initTree method by KevinBatdorf · Pull Request #1648 · alpinejs/alpine · GitHub

c4710n

c4710n

This workaround would be fine if the Alpine component only contains shadowed assigns of LiveView.

But, if the Alpine component is maintaining its own states besides of the shadowed assigns of LiveView. Every time the shadowed assigns of LiveView updates, the Alpine component’s own states will be reset by window.Alpine.initTree(to).

That’s not expected.

For example:

<div x-data={"{ count: #{@count} }"}></div>                # fine
<div x-data={"{ count: #{@count}, showTip: true }"}></div> # bad

So, I prefer the workaround provided by simoncocking.

Last Post!

mchlsync

mchlsync

I got this error if I use <%= @count %>. I tried x-data={"{ count: #{@count} }" too but it doesn’t show the actual value in Chrome dev tool. it is not working as well.

Make sure the attribute is properly closed. This may also happen if
there is an EEx interpolation inside a tag, which is not supported.
Instead of

<div <%= @some_attributes %>>
</div>

do

<div {@some_attributes}>
</div>

Where @some_attributes must be a keyword list or a map.

Where Next?

Popular in Questions Top

rms.mrcs
Hi, I need to transform a list of numbers into a map where the keys are the indexes and the values are the original values of the list. ...
New
jononomo
I am trying to figure out how Mix knows whether the environment is test, dev, or prod – where is this set? Thanks.
New
Qqwy
Original source of discussion: This topic on the Pragmatic Programmers’ Functional Web Development with Elixir, OTP, and Phoenix forum. ...
New
Lily
In templates/appointment/index.html.eex: &lt;%= for appointment &lt;- @appointments do %&gt; &lt;tr&gt; &lt;td&gt;&lt;%= appoi...
New
WestKeys
Currently suffering from paralysis by [HTTP client] analysis. This is rather unusual in Elixirland as there tends to be consensus on the ...
New
Harrisonl
We have an ECS cluster with 4 services, where each task joins a single cluster, via discovery ECS discovery service. Currently when I de...
New
jason.o
In the code below, if the create action is not set to accept “extra_key” as an input, it errors out with a message shown above. Is there ...
New

Other popular topics Top

electic
Hi, I am new to Elixir. I am trying to use the DateTime component to insert a date into MySQL however the there seems to be no way to fo...
New
stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
Darmani72
If I have a post route which an argument: post /my_post_route/:my_param1, MyController.my_post_handler How would get the post params ...
New
joeerl
Hello again - after a longish gap I’ve decided I really must dig into Elixir and see what’s been happening here - so I have a few questio...
New
gshaw
What is the idiomatic way of matching for not nil in Elixir? E.g., First way: defp halt_if_not_signed_in(conn, signed_in_account) when...
New
AstonJ
Posting this to see if we can make things easier for people to get into Neovim. If you use Neovim and have a favourite distro please let ...
New

We're in Beta

About us Mission Statement