`Phoenix.LiveView.JS` for client-side interactions

almirsarajcic

almirsarajcic

6 hours ago

0 comments

Not every click needs a server roundtrip. Toggling a dropdown, showing a modal, or hiding a deleted item are all pure UI operations — but they’re often implemented with handle_event/3 and a socket assign, adding latency for no reason.

Phoenix.LiveView.JS lets you execute these DOM operations entirely on the client, with no server ping.

alias Phoenix.LiveView.JS

# ❌ Server roundtrip just to toggle visibility
def handle_event("toggle_menu", _params, socket) do
  {:noreply, assign(socket, :show_menu, !socket.assigns.show_menu)}
end

# ✅ Pure client-side, zero server roundtrip
def toggle_menu(js \\ %JS{}) do
  JS.toggle(js, to: "#dropdown-menu")
end

In your template, replace phx-click="toggle_menu" with phx-click={toggle_menu()}. The server never sees this event.

JS commands are also chainable, which makes modal animations clean to implement:

alias Phoenix.LiveView.JS

def show_modal(js \\ %JS{}) do
  js
  |> JS.show(to: "#modal-backdrop")
  |> JS.show(
    to: "#modal",
    transition: {"ease-out duration-200", "opacity-0 scale-95", "opacity-100 scale-100"}
  )
  |> JS.add_class("overflow-hidden", to: "body")
end

def hide_modal(js \\ %JS{}) do
  js
  |> JS.hide(to: "#modal-backdrop")
  |> JS.hide(
    to: "#modal",
    transition: {"ease-in duration-150", "opacity-100 scale-100", "opacity-0 scale-95"}
  )
  |> JS.remove_class("overflow-hidden", to: "body")
end

You can also combine JS commands with a server push — useful when you want optimistic UI (hide immediately) while the server does the real work:

# Hide the item AND tell the server to delete it
def handle_delete(js \\ %JS{}, id) do
  js
  |> JS.hide(to: "#item-#{id}", transition: "fade-out")
  |> JS.push("delete_item", value: %{id: id})
end

The full set of available commands: JS.show/2, JS.hide/2, JS.toggle/2, JS.add_class/3, JS.remove_class/3, JS.toggle_class/3, JS.set_attribute/3, JS.remove_attribute/3, JS.toggle_attribute/3, JS.dispatch/3, JS.transition/3, and JS.push/3.

Note: JS commands are DOM-patch aware — operations applied client-side survive LiveView diffs and re-renders, unlike manual DOM manipulation in JavaScript hooks.

Comments (0)

Sign in with GitHub to join the discussion