Phoenix LiveView JavaScript Client

repository·main·Indexed 27 days ago

https://github.com/phoenixframework/phoenix_live_view

The JavaScript client for Phoenix LiveView (version 1.2.8), providing a low-level API for advanced interoperability with server-rendered HTML over WebSockets. It includes tools for managing LiveSocket options, implementing custom hooks via createHook, handling external uploads to providers like Amazon S3, and integrating chunked HTTP uploads using UpChunk.

Tokens
32K
Snippets
65
Records
193
Agent score
92%

What's inside phoenix_live_view

  1. Navigate between multiple LiveViews on a page

    main

    You can render multiple LiveViews on a single page using Phoenix.Component.live_render/3.

    Important Limitation: Only LiveViews defined directly in your router can use the Live Navigation functionality (patch and navigate). LiveViews rendered via live_render/3 (nested LiveViews) cannot use these navigation helpers to navigate to other routes, as they are not part of the primary routing structure.

  2. Retrieve locale from URL parameters

    main

    You can include the locale as a parameter in your router scope. To ensure the locale is applied to every LiveView automatically, use a on_mount hook.

    Note: Because the Gettext locale is not stored in the socket assigns, you must use <.link navigate={...} /> to change the locale via navigation rather than patching the page.

    # 1. Define the scope in your router
    scope "/:locale" do
      live "/", HomeLive
    end
    
    # 2. Create an on_mount hook to restore the locale
    defmodule MyAppWeb.RestoreLocale do
      def on_mount(:default, %{"locale" => locale}, _session, socket) do
        Gettext.put_locale(MyApp.Gettext, locale)
        {:cont, socket}
      end
    
      # catch-all case for routes without a locale
      def on_mount(:default, _params, _session, socket), do: {:cont, socket}
    end
    
    # 3. Apply the hook in your LiveView module
    defmodule MyAppWeb do
      def live_view do
        quote do
          use Phoenix.LiveView
          on_mount MyAppWeb.RestoreLocale
          unquote(view_helpers())
        end
      end
    end
  3. Manage LiveView state during deployments using query parameters

    main

    To prevent state loss during deployments or reconnections, move transient UI state (like active tabs) into the URL using query parameters. Instead of using phx-click and handle_event/3 to update internal assigns, use <.link patch={...}> to update the URL. This allows the new server instance to reconstruct the state via handle_params/3 upon reconnection.

    Benefits:

    • Reduces server-side state.
    • Enables shareable links and improved SEO.
    • Makes the application more resilient to disconnections.
  4. Prevent form submission using a Client Hook

    main

    To implement client-side validation that prevents a phx-submit from reaching the server, use a Phoenix Hook to intercept the submit event and call event.stopPropagation() and event.preventDefault().

    Implementation Example

    1. Define the Hook:
    /**
     * @type {import("phoenix_live_view").HooksOptions}
     */
    let Hooks = {}
    Hooks.CustomFormSubmission = {
      mounted() {
        this.el.addEventListener("submit", (event) => {
          if (!this.shouldSubmit()) {
            // prevent the event from bubbling to the default LiveView handler
            event.stopPropagation()
            // prevent the default browser behavior (submitting the form over HTTP)
            event.preventDefault()
          }
        })
      },
      shouldSubmit() {
        // Implement client-side validation logic here
        return true
      }
    }
    1. Attach the Hook to the Form:
    <form id="my-form" phx-hook="CustomFormSubmission">
      <input type="text" name="text" value={@text}>
    </form>
    /**
     * @type {import("phoenix_live_view").HooksOptions} */
    let Hooks = {}
    Hooks.CustomFormSubmission = {
      mounted() {
        this.el.addEventListener("submit", (event) => {
          if (!this.shouldSubmit()) {
            // prevent the event from bubbling to the default LiveView handler
            event.stopPropagation()
            // prevent the default browser behavior (submitting the form over HTTP)
            event.preventDefault()
          }
        })
      },
      shouldSubmit() {
        // Check if we should submit the form
        ...
      }
    }
  5. Trigger standard HTTP form submission with phx-trigger-action

    main

    If you need to perform a standard HTTP POST (e.g., for operations requiring Plug session mutation like password resets) after performing LiveView-side validation, use the phx-trigger-action attribute.

    Set phx-trigger-action to a boolean assign. When that assign becomes true, LiveView will disconnect and submit the form to the URL specified in the form's action attribute.

    <%-- Template --%>
    <.form :let={f} for={@changeset}
      action={~p"/users/reset_password"}
      phx-submit="save"
      phx-trigger-action={@trigger_submit}>
    
    <%-- LiveView --%>
    def handle_event("save", params, socket) do
      case validate_change_password(socket.assigns.user, params) do
        {:ok, changeset} ->
          {:noreply, assign(socket, changeset: changeset, trigger_submit: true)}
    
        {:error, changeset} ->
          {:noreply, assign(socket, changeset: changeset)}
      end
    end
  6. Handle expected error scenarios in LiveView

    main

    For errors that are expected as part of normal user flow (e.g., invalid form data), do not use exceptions. Instead, manage the error state within your LiveView assigns and render error messages in the UI. You can also use put_flash/3 to provide feedback to the user.

    Example of handling a business logic failure using put_flash:

    if MyApp.Org.leave(socket.assigns.current_org, member) do
      {:noreply, socket}
    else
      {:noreply, put_flash(socket, :error, "last member cannot leave organization")}
    end
    if MyApp.Org.leave(socket.assigns.current_org, member) do
      {:noreply, socket}
    else
      {:noreply, put_flash(socket, :error, "last member cannot leave organization")}
    end
  7. Use Click Events to trigger server events

    main

    Use the phx-click attribute to send click events to the server, which are handled via the handle_event/3 callback.

    To send additional data with the click, you can:

    1. Use Phoenix.LiveView.JS.push/3 to specify a custom value.
    2. Use phx-value-* attributes to send a map of parameters.
    3. Configure LiveSocket metadata to include client-side information like mouse coordinates.

    The phx-click-away attribute can be used to trigger an event when a user clicks outside of a specific element (useful for dropdowns).

  8. Implement infinite scrolling with phx-viewport-top and phx-viewport-bottom

    main

    To implement infinite scrolling or virtualized lists, use the viewport bindings to detect when the user reaches the boundaries of a container:

    • phx-viewport-top: Triggers when the first child reaches the top of the viewport.
    • phx-viewport-bottom: Triggers when the last child reaches the bottom of the viewport.

    When a user 'overruns' the viewport (e.g., scrolling rapidly past the boundary), the event includes a special parameter "_overran" => true. This can be used to reset pagination state.

    Testing: Use Phoenix.LiveViewTest.render_hook/3 to test these events in your test suite.

    # Testing viewport events
    view
    |> element("#posts")
    |> render_hook("next-page")
  9. Use comprehensions and :key for optimized lists

    main

    HEEx supports comprehensions via <%= for ... %> or the :for attribute. To optimize change tracking in collections (so that inserting an item doesn't cause all subsequent items to re-render), provide a unique :key using the post.id or similar unique identifier.

    Example with :key attribute:

    <section :for={post <- @posts} :key={post.id}>
      <h1>{expand_title(post.title)}</h1>
    </section>

    Note: Tracking changes in comprehensions requires extra server memory. For very large collections, consider using Phoenix.LiveView.stream/4 to manage data without keeping the entire collection in memory.

  10. Configure external uploads in Phoenix LiveView

    main

    To upload files directly to external cloud providers (like Amazon S3 or Google Cloud), use the :external option in Phoenix.LiveView.allow_upload/3.

    You must provide a 2-arity function that generates metadata for the upload. This function is called by the server and returns a map (meta) which is then passed to a corresponding JavaScript uploader on the client side.

    The function must return either {:ok, meta, socket} or {:error, meta, socket}, where meta is a map containing at least a :uploader key that matches a registered JavaScript uploader.

    def mount(_params, _session, socket) do
      {:ok,
       socket
       |> assign(:uploaded_files, [])
       |> allow_upload(:avatar, accept: :any, max_entries: 3, external: &presign_upload/2)}
    end
    
    defp presign_upload(entry, socket) do
      # Generate metadata (e.g., a pre-signed URL)
      meta = %{uploader: "MyUploader", url: "https://example.com/upload"}
      {:ok, meta, socket}
    end
  11. Configure the App Layout

    main

    The app layout is the dynamic part of your application (containing menus, sidebars, etc.) that updates during live navigation.

    • In Phoenix v1.8+: The app layout is explicitly rendered within your templates by calling the <Layouts.app /> component.
    • In Phoenix v1.7 and earlier: The layout was typically configured in lib/my_app_web.ex using use Phoenix.LiveView, layout: ....

    Layouts are typically located in components/layouts and are embedded within MyAppWeb.Layouts.