Rubas

Rubas

Proper AlpineJS Integration with Liveview

Problem

When using more complex nested components, especially with live_components, AlpineJS can stop working after updates.

This issue is also documented here:

https://github.com/alpinejs/alpine/issues/911

Source

All available documentation instructs you to integrate Alpinejs in the app.js within the onBeforeElUpdated hook like this.

// assets/js/app.js
let liveSocket = new LiveSocket("/live", Socket, {
  params: {_csrf_token: csrfToken},
  dom: {
    onBeforeElUpdated(from, to) {
      if (from._x_dataStack) {
        window.Alpine.clone(from, to)
      }
    }
  }
})

This doesn’t factor in that LV updates can be nested html elements!

Solution

You need to clone the Alpine data not only on the root element but also in all child elements .

Here is our solution.

// assets/ts/liveview.ts
const cloneAlpineJSData: (from: HTMLElement, to: HTMLElement) => void = (from, to) => {
  if (!window.Alpine || !from || !to) return

  for (let index = 0; index < to.children.length; index++) {
    const from2 = from.children[index]
    const to2 = to.children[index]

    if (from2 instanceof HTMLElement && to2 instanceof HTMLElement) {
      cloneAlpineJSData(from2, to2)
    }
  }

  if (from._x_dataStack) window.Alpine.clone(from, to)

  // Set `x-persist="type,style.height,style.minHeight"` to prevent these attributes from being overrided by an LV update
  persistAttributes(from, to, from.getAttribute('x-persist'))
}

[...]

const liveSocket = new LiveSocket('/live', Socket, {
    hooks,
    params: { _csrf_token: csrfToken },
    dom: {
      onBeforeElUpdated(from, to) {
        cloneAlpineJSData(from, to)

        return true
      }
    }
  })

Note:
persistAttributes is an another helper which, persist certain attributes which are updated by Alpinejs. For example we have a <textarea> that changes style.height and style.minHeight depending on the entered data.

Most Liked

Rubas

Rubas

A month later, I must say if you run into this issue like this, you probably fell too much in love with Alpinejs (I did) and you will have more and more issues and poor performance for complex and bigger elements.

We ported now everything over to LiveView.JS, added all the missing functionality (push_js, hooks, eventListeners and so on) our self and ditched Alpinejs completely.

Definitely worth it! Would strongly suggest it.

10
Post #5
krishandley

krishandley

Just in case this helps… I was playing with Alpine again and found a way around a few issues I had previously had with something similar.

Instead of having variables inside x-data, use an attribute and a mutation observer.

<div x-data="{foo: null}" foo={@foo} />
init() {
  this.foo = this.$el.getAttribute("foo");

  this.observer = new MutationObserver((mutations) => {
    mutations.forEach((m) => {
      if (m.attributeName == "foo") {
        this.foo = this.$el.getAttribute("foo");
      }
    });
  });

  this.observer.observe(this.$el, { attributes: true });
}
v1d3rm3

v1d3rm3

To me, the big problem with liveview is missing a source of truth to UI behavior. It’s very frustraing to choose working with ui state in server state. When disconnection occurs, all data is cleaned, or you have to implement a very complex way to mantain state.

An alternative solution is to implement UI state with bare Javascript. And, it’s so annoying when you already used client side framework/libraries like React/Angular. For me, LiveView is not a tool for everything. Complex javascript UI’s needs, it’s better using phoenix only to build API

Last Post!

geoffrey

geoffrey

Hey, did you find any issues to that problem? I got the same and the only solution I found is:

let liveSocket = new LiveSocket("/live", Socket, {
  params: { _csrf_token: csrfToken },
  dom: {
    // Initialize Alpine on newly added nodes
    onNodeAdded(node) {
      if (node._x_dataStack) {
        window.Alpine.initTree(node)
      }
    },
    // Preserve and reinitialize Alpine state on element updates
    onBeforeElUpdated(from, to) {
      if (from._x_dataStack) {
        window.Alpine.clone(from, to)
        window.Alpine.initTree(to)
      }
    }
  }
})

Where Next?

Popular in Questions Top

lanycrost
Hi everyone! I need implement if…else if…else condition from my elixir code, and anymore of this control flow structures not work proper...
New
Lily
In templates/appointment/index.html.eex: &lt;%= for appointment &lt;- @appointments do %&gt; &lt;tr&gt; &lt;td&gt;&lt;%= appoi...
New
Fl4m3Ph03n1x
About me? ( if you have nothing better to do than reading about some random guy in the internet :stuck_out_tongue: ) Hello all, this is ...
New
greenz1
I have a phoenix application from which a user can download multiple(5-6) files of size 1MB. I couldn’t find anything related to sending ...
New
sen
Hi All, I set a environment variables in dev.exs , like below code. when i start server, how can i set the ${enable} value? thanks. d...
New
komlanvi
Hi everyone, I was playing with phoenix liveView but I run into an issue. I have a form and want to validate each input text when the te...
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

Other popular topics Top

nobody
Hi! In PHP: $_SERVER[‘SERVER_ADDR’] - in Elixir? Searched the docs for ip address and the web, no good results. Thanks!
New
JeremM34
Hello, how can I check the Phoenix version ? Thanks !
New
baxterw3b
Hi guys, i’m new in the Elixir world, and i have to say, that i love it! i’m having some problem to understand anonymous functions with ...
New
Brian
What is the proper way to load a module from a file in to IEX? In the python world, doing something like this pretty standard: from ....
New
lanycrost
Hi everyone! I need implement if…else if…else condition from my elixir code, and anymore of this control flow structures not work proper...
New
TunkShif
This post is an instruction guide to help you setup your Neovim for Elixir development from scratch. It includes general information on h...
274 42533 114
New

We're in Beta

About us Mission Statement