#Phoenix Liveview - infinite scroll element re-renders
4 messages · Page 1 of 1 (latest)
Badge html
def render(assigns) do
~H"""
<.live_component
module={PlayforstakeWeb.AppBar.Components.Notifications.Popover}
user_id={@user_id}
id="user-notifications"
/>
<div
class="cursor-pointer flex hover:bg-slate-700 border border-[#22292f] rounded-xl h-10 w-10 justify-center items-center"
phx-hook="Popover"
id="user_notifications_button"
>
<.icon name="hero-bell" class="text-white h-6 w-6" />
<span class="sr-only">Notifications</span>
<div class="absolute inline-flex items-center justify-center w-5 h-5 text-xs font-bold text-white bg-red-500 border-white rounded-full -top-3 -end-2 dark:border-gray-900">
<%= @unread_notifications %>
</div>
</div>
"""
end
popover html:
~H"""
<div
id="user-notifications"
role="tooltip"
class="z-index-10 absolute mt-[185px] invisible inline-block w-64 text-sm text-gray-500 transition-opacity duration-300 bg-[#182029] border border-[#22292f] rounded-lg shadow-sm "
>
<div class="px-3 py-2 bg-[#182029] z-index-10 border-b border-[#22292f] rounded-t-lg">
<h4 class="pb-5">Notifications</h4>
<ul
id={"notifications-#{UUID.uuid4()}"}
phx-update="stream"
phx-target={@myself}
phx-viewport-top={@page > 1 && "prev-page"}
phx-viewport-bottom={!@end_of_timeline? && "next-page"}
class={[
"flex flex-col max-h-[410px] overflow-y-auto",
if(@end_of_timeline?, do: "pb-10", else: "pb-[calc(20vh)]"),
if(@page == 2, do: "pt-10", else: "pt-[calc(20vh)]")
]}
>
<li
:for={{dom_id, notification} <- @streams.notifications}
id={dom_id}
class="flex flex-col text-center items-center p-1 border-b border-t border-[#22292f] rounded-t-lg"
>
<h7 class="font-bold">New reward</h7>
<p class="text-[14px] font-bold">
You have been given a YYYZ that you can use in your next match.
</p>
</li>
</ul>
</div>
</div>
"""
Popover hooks js
let currentActivePopover = null;
Hooks.Popover = {
tooltipTimeout: null,
delay: 100, // Delay in milliseconds before hiding the tooltip
mounted() {
this.initializePopover(this.el);
},
updated() {
this.initializePopover(this.el);
},
initializePopover(el) {
const tooltipId = el.dataset.target_id;
const placement = el.dataset.tooltip_placement || "bottom"
const tooltipEl = document.getElementById(tooltipId);
const options = {
placement: placement,
triggerType: 'none',
onHide: () => {
},
onShow: () => {
},
onToggle: () => {
},
};
const instanceOptions = {
id: 'tooltipContent',
override: true
};
const tooltip = new Tooltip(tooltipEl, el, options, instanceOptions);
// Hide any active popover and show the new one
const showTooltip = () => {
clearTimeout(this.tooltipTimeout);
if (currentActivePopover && currentActivePopover !== tooltip) {
currentActivePopover.hide(); // Hide the currently active popover
}
tooltip.show();
currentActivePopover = tooltip; // Set the new popover as the current active popover
};
// Hide the popover after a delay
const hideTooltip = () => {
this.tooltipTimeout = setTimeout(() => {
if (currentActivePopover === tooltip) {
tooltip.hide();
currentActivePopover = null;
}
}, this.delay);
};
// Setup event listeners
el.addEventListener('mouseenter', showTooltip);
el.addEventListener('mouseleave', hideTooltip);
tooltipEl.addEventListener('mouseenter', () => {
clearTimeout(this.tooltipTimeout);
});
tooltipEl.addEventListener('mouseleave', hideTooltip);
}
}
I removed the "hidden" attribute from the element on paginate_posts 🙂
in general, you need to make sure your client-side changes are reflected server-side when the server decides to re-render, otherwise your client-side only changes get wiped out. Another way you can handle this is to use JS to make the changes client-side right-away, but still send a message to the server to let it know the state change so that if/when the server re-renders it also knows to add (or not add) the "hidden" class back