A simple example of Alpine.js code is not working properly in Phoenix 1.6 LiveView

Is there an alternative to Alpine.js in Phoenix 1.6?

I am trying to use this simple example:

<div x-data="{ open: false }">

    <button x-on:click="open = ! open">Expands</button>
 
    <div x-show="open" x-transition>
        Content...
    </div>

</div>

But in my heex template file in my live view it is expanded by default, instead of hidden. Why? In a non live view heex template file it works as expected.

Any idea why LiveView expands it by default on entering the page?

If this problem couldn’t be solved, could you suggest an alternative to Alpine.js that works with the latest Phoenix 1.6 LiveView?

I use similar code in a project, with Phoenix 1.6 and Heex templates, no issues. It’s probably something else. You don’t have errors in the browser console (or the server) ?

No errors. It works with get request using a standard controller but not with live in router. The code is the same, but for some reason the button’s open is in true state or something when the page loads. It is expanded on the page reload. Unlike the non liveview page in my website. It is the same project, just a different route, so

get "/", PageController, :index  
live "/subpage", SubpageLive

I mean it works and I can toggle it in the live view page as well. But the initial state is true instead of false - at least I think it is. And the code is identical. I have tried different buttons and content and it’s the same. Always expanded at default.

Other components using Alpine are working ?

I have tried this example:

<button x-data="{ label: 'Click Here' }" x-text="label"></button>

and the button is empty without label.

Ok, can you show your app.js, where you’re initializing Alpine on the client ?

I load the script not in app.js but inside of head in root.html.heex

Just before the end of </head> I put the standard:

<script defer src="https://unpkg.com/alpinejs@3.7.0/dist/cdn.min.js"></script>

Could it be that LiveView has problem with scripts between the <head></head> tags and the non live view doesn’t mind?

The thing is, for Alpine et LiveView to work properly together, you need to plug Alpine into the LiveSocket like this:

import Alpine from "alpinejs";

window.Alpine = Alpine;
Alpine.start();

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

It might be that once the Livesocket connects, and sends back the result, it overrides Alpine applied styles (display: none; in this case), and since Alpine isn’t aware of it, it can’t stop it from happening.

4 Likes

I tried to remove defer from it but without any change.

In fact, now it says in the browser console this:

cdn.min.js:5 Alpine Warning: Unable to initialize. Trying to load Alpine before <body> is available. Did you forget to add defer in Alpine’s <script> tag?

How do you load Alpine in your project? In the root.html.heex (before the </head> tag) as well, like me?

Nope. And i don’t think you can do it like this. LiveView and Alpine need to be aware of each other (see my previous updated posts).

In which file I have to add that code? app.js?

Yes. You do have to install AlpineJs before though (npm install alpinejs in your asset folder).

I am using Phoenix 1.6. I don’t have npm stuff inside my Phoenix project.

Can I download the file https://unpkg.com/alpinejs@3.7.0/dist/cdn.min.js and put it in the vendor folder like the topbar.js file?

and import it like:

import topbar from "../vendor/topbar";

You can just download the latest tag of Alpine then (not sure which file you’ll need, i don’t do it like this), and put that file next to your app.js in order to import it.
Edit: yes, you can do like for topbar :wink:

OK, I will try it. I will probably put it in the wrong position but I will try.

YES!!! It finally works. Thanks a lot.

For people who will encounter similar problem your app.js should look something like this:
Also, don’t forget to download the alpine.js file and save it in assets/vendor/ folder as alpine.js or something like that.

import topbar from "../vendor/topbar";
import Alpine from "../vendor/alpine";

// Alpine
window.Alpine = Alpine;
Alpine.start();

let csrfToken = document
  .querySelector("meta[name='csrf-token']")
  .getAttribute("content");
let liveSocket = new LiveSocket("/live", Socket, {
  params: { _csrf_token: csrfToken },
  dom: {
    onBeforeElUpdated(from, to) {
      if (from._x_dataStack) {
        window.Alpine.clone(from, to);
      }
    },
  },
});
6 Likes