alpine: x-open not working for dynamically injected html
I ran into an issue with Alpine and Phoenix’s LiveView.
LiveView will dynamically render & replace html in a page. The issue I ran into is that a simple dropdown menu (from TailwindUI) would be in the ‘closed’ state, but the menu was still displaying. Clicking the menu toggle once would keep it open, and clicking it again would finally close it.
So alpine seemed to be initialized to the correct state, but x-show="open"
did not have an effect initially.
My workaround was to manually set the dropdown to display: none;
so that it would not initially be displayed.
Any ideas on a good way to handle this? Is there a way to tell alpine to ‘initialize’ a component again?
About this issue
- Original URL
- State: closed
- Created 4 years ago
- Comments: 24 (4 by maintainers)
Ran into the same thing today. Quick hack was to initialize components after inserting.
Seems like the
listenForNewUninitializedComponentsAtRunTime
isn’t picking them up correctly somewhere. I don’t know enough about MutationObservers to say why.Hey all you LiveViewers, I just posted a pretty big response on the subject of making LiveView integrate with Alpine here: https://github.com/phoenixframework/phoenix_live_view/issues/809#issuecomment-632366710
Take a look!
Looking at the source code help me found the best solution.
Forget everything about what I wrote before. Here the solution fixing all issues about
x-ref/$refs
, input loosing focus, …The HTML part, keep it simple and near of Alpine-original:
The JS part (without any lib):
If you are experiencing issues, put
force_render
totrue
.Note: I’m still playing with the
xcloak
attributeHope that helps ! 😃
I’ve got another issue which I think might be related. I load data in a list after user input and have alpine sprinkled in the list items to expand collapse additional details as well as controls for deleting subitems. (So the double render “should” not be a problem) On delete of the sub item the whole list is rerendered (intentionally) however suddenly all items in the list have their subitems expanded. I’ll investigate some more and report back
I’m seeing the same thing in a Phoenix Liveview. Tried to add the Alpine.discoverUninitializedComponents(function(el){ Alpine.initializeComponent(el) }) in a Liveview update hook but to no avail.
Based on multiple occurrences, I’m going to say this is a bug and needs looking into. Not sure who’s got time for this, @SimoTod @HugoDF @calebporzio .
I might get a chance to look into this, this week.
For anyone trying the suggested fix in https://github.com/phoenixframework/phoenix_live_view/issues/809#issuecomment-637671803
and wondering why it isn’t working, it’s because
dom
option is added inv0.1.13
and it hasn’t been released yet.Hey 👋
Sometime, the DOM inside a Alpine Component is rerendered by dynamic injected html without being detected by Alpine and cause Alpine to stop incorrectly or loose state like for
x-show
. Tweaking withid="<%= if connected?(@socket), do: "connected-id", else: "not-connected-id" %>"
doesn’t always works or doesn’t work at all for some cases.Here is my solution for Phoenix Live View. But it can be used with all dynamically injected template I guess (of course, it so shall be adapted).
First attempt
First, I was trying to use phoenix live view hooks to tell Alpine to rerender the actual component. I had so:
And the js file with the Phoenix hooks:
I added the
style="display: none;"
attribute to the second tab because it wasn’t correctly initialized / it blinks at page load. But theAlpine.initializeComponent
didn’t work.Second attempt
If the
Alpine.initializeComponent
is not working it’s because the id is the same.I achieve to make something pretty nice with generating random id and tell Alpine to initialize the “new” component.
Note: Maybe the
guidGenerator
is too high-cpu consumption (you could find an alternative to generate random ids like the name + timestamp). Also, maybe Alpine keep track of old ids for old components, if Alpine didn’t detect the removed component, there will be a high memory leak. DoingAlpine.start();
instead ofAlpine.initializeComponent()
may be a better approach ? To be confirmed But I’m still losing selected tab, at each page rerender I’m falling to the default ‘chat’ tab.Final and working attempt
To cheat the Alpine system, I moved the
x-data
value in the jswindow
object. And withx-init
Alpine attribute, I watch for updates and copy it to thewindow x-data
attribute.Note that I removed the
x-data
attribute in the HTML file so Alpine will not initialize the component at the page load, it still be injected by js during themounted
hook. Also, I keep thestyle="display: none;"
to the second tab because the second tab blinks cause of the multiple Alpine render (because Live View is rerender twice before and after the livesocket connexion).There still have issues. If an input is in the component, we loose the focus, but it can be fixed with JS (again…).
Hope that can help someone !
EDIT: Instead of playing with
style="display: none;"
we can play with x-cloak attribute.EDIT 2: index.js#L40 and index.js#L79 can answer my question about memory leak. If I understand, components aren’t registered in some unknown variable, it’s directly integrated to the HTML DOM element so, if we re-initialize a component, it will override that variable. I may be wrong. But by looking at the source code, I don’t understand why changing the
id
can re-initialize the component.EDIT 3: To make my code more generic, I added a
x-name
attribute referencing thewindow.AlpineComponents
tab index to getx-data
from.EDIT 4: I’m still running into issues with
x-ref
inside re-rendered Alpine components… They’ren’t detected, got:$refs.my_ref is undefined
.