Javascript Hooks
Hook structure
We have set up a structure for you that makes it easy to keep adding hooks. To add a new hook:
-
touch /assets/js/hooks/my-hook.js - Make sure you export it:
const MyHook = {
// ...
};
export default MyHook;
-
In
hooks/index.js, import it:
import MyHook from "./my-hook";
export default {
MyHook,
};
It will now be available to use in your HEEx templates:
<div phx-hook="MyHook"></div>
Note that in dead views, only the mounted() callback will be run.
Hooks from Petal Pro
RemoveFlashHook
Pairs with the <.flash_group> component and will auto-dismiss a flash message after a period of time (10 seconds).
ResizeTextareaHook
Textareas can be annoying when your content exceeds the height and a scrollbar appears. This hook auto-resizes the textarea as you type so it never shows a scrollbar and keeps getting bigger as you add more content.
ScrollTopHook
Useful when you have a LiveView with multiple “pages” navigated via live_patch links. Without this, clicking a live_patch link while scrolled down won’t reset the scroll position, leaving the user disoriented. Place phx-hook="ScrollTopHook" on the live_patch link and it will scroll to the top on click.
New hooks in v4
InfiniteScrollHook
Triggers a load_more LiveView event when a sentinel element scrolls into view. Uses IntersectionObserver with a 200px root margin so content loads slightly before the user reaches the bottom.
Usage:
<div
id="infinite-scroll-marker"
phx-hook="InfiniteScrollHook"
data-page={@page}
/>
In your LiveView, handle the event:
def handle_event("load_more", %{"page" => page}, socket) do
page = String.to_integer(page)
items = MyContext.list_items(page: page + 1)
{:noreply, stream(socket, :items, items)}
end
The hook re-observes the element after each LiveView patch, so it works correctly with streams.
ScrollToBottomHook
Auto-scrolls a container to the bottom on mount and after every update. The canonical use case is a chat window where new messages should always be visible.
Usage:
<div
id="chat-messages"
phx-hook="ScrollToBottomHook"
phx-update="stream"
class="overflow-y-auto h-96"
>
<div :for={{id, message} <- @streams.messages} id={id}>
<%= message.body %>
</div>
</div>
ScrollToHashHook
On mount, smoothly scrolls to the element matching the URL hash (window.location.hash). Useful for documentation pages, settings pages, or any page with anchor links.
Usage: place the hook on the page root element or any wrapper:
<div id="page-content" phx-hook="ScrollToHashHook">
<section id="billing">...</section>
<section id="notifications">...</section>
</div>
Linking to ?#billing will cause the page to smooth-scroll to that section on load.
SelectAllHook
Implements the “select all” checkbox pattern for bulk-action tables. When the hook’s element is checked, it sets all matching row checkboxes to the same state and fires a change event on each so LiveView bindings pick it up.
Data attribute:
-
data-target— CSS selector for the row checkboxes (required)
Usage:
<%!-- Select-all control in the table header --%>
<input
type="checkbox"
id="select-all"
phx-hook="SelectAllHook"
data-target=".row-checkbox"
/>
<%!-- Per-row checkboxes --%>
<input
type="checkbox"
class="row-checkbox"
phx-click="toggle_selected"
phx-value-id={item.id}
/>
ShareHook
Uses the Web Share API to trigger the native OS share sheet on mobile. Falls back to copying the URL to the clipboard on desktop browsers that don’t support navigator.share. On clipboard copy, it pushes a link_copied event to LiveView so you can show a flash message.
Data attributes (all optional):
-
data-title— share title (defaults todocument.title) -
data-text— share body text -
data-url— URL to share (defaults towindow.location.href)
Usage:
<button
phx-hook="ShareHook"
id="share-btn"
data-title="Check this out"
data-text="An interesting article"
data-url={~p"/blog/#{@post.slug}"}
>
Share
</button>
Handle the clipboard fallback in LiveView:
def handle_event("link_copied", _params, socket) do
{:noreply, put_flash(socket, :info, "Link copied to clipboard!")}
end