Phoenix live view 0.12.1 phx-change not fired

the app worked with phoenix live view 0.5 and phoenix 1.4.
after upgrade to 0.12.1 and phoenix 1.5 everything seems ok and the live view is mounted but the phx-change event attached to the form seems to never trigger
when an input is entered in the various form fields

how to debug that ?
is there a way to trace the events fired on the js side ?

thanks

We need to see your code to say more. What does the LiveView and template look like? Assuming you have exposed your liveSocket instance on window, which is what new applications do, you can call liveSocket.enableDebug() in the js console.

Hello thank you for your response. Here are the relevant parts of the code.

Here is the live view

defmodule MyappWeb.ExampleLive do
  use Phoenix.LiveView

  def mount(_params, session, socket) do
  # this code is triggered and connection is ok
  IO.inspect(connected?(socket), label: "CONNECTION STATUS")
    {
      :ok,
      assign(
        socket,
        %{
          "key" => value
        }
      )
    }
  end

  def handle_event("validate", value, socket) do

  # this code is not triggered
 IO.inspect(connected?(socket), label: "HANDLE_PHX_CHANGE")
   
    newval = value["response"]["response"]

    {:noreply, assign(socket, response: newval )}

  end
  ...

next here is the app.js


import css from "../css/app.css";


import "phoenix_html";

// IE11 Support for LiveView
import "mdn-polyfills/CustomEvent";
import "mdn-polyfills/String.prototype.startsWith";
import "mdn-polyfills/Array.from";
import "mdn-polyfills/NodeList.prototype.forEach";
import "mdn-polyfills/Element.prototype.closest";
import "mdn-polyfills/Element.prototype.matches";
import "child-replace-with-polyfill";
import "url-search-params-polyfill";
import "formdata-polyfill";
import "classlist-polyfill";


import {Socket} from "phoenix";
import {LiveSocket} from "phoenix_live_view";
import NProgress from "nprogress";
let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content");
let liveSocket = new LiveSocket("/live", Socket, {params: {_csrf_token: csrfToken}});
window.addEventListener("phx:page-loading-start", info => NProgress.start())
window.addEventListener("phx:page-loading-stop", info => NProgress.done())
liveSocket.connect();

// expose liveSocket on window for web console debug logs and latency simulation:
liveSocket.enableDebug()
liveSocket.enableLatencySim(1000)
window.liveSocket = liveSocket

Next here are the general configs

defmodule MyappWeb.Endpoint do
 use Phoenix.Endpoint, otp_app: :myapp

 @session_options [
   store: :cookie,
   key: "_myapp_key",
   signing_salt: "BAjfAPWj"
 ]
 socket "/socket", MyappWeb.UserSocket,
   websocket: true,
   longpoll: false

 socket "/live", Phoenix.LiveView.Socket,
 websocket: [connect_info: [session: @session_options]]

it’s not clear why a new live_view function is needed, previously it was merged with view
the template live.html does not exist there is only the regular app layout template. Maybe this is the reason ?

defmodule MyappWeb do

...
 def live_view do
   quote do
     use Phoenix.LiveView,
       layout: {MyappWeb.LayoutView, "live.html"}

     unquote(view_helpers())
   end
 end

 def live_component do
   quote do
     use Phoenix.LiveComponent

     unquote(view_helpers())
   end
 end


 def router do
   quote do
     use Phoenix.Router
     import Plug.Conn
     import Phoenix.LiveView.Router
     import Phoenix.Controller
   end
 end

 def channel do
   quote do
     use Phoenix.Channel
     import MyappWeb.Gettext
   end
 end

 defp view_helpers do
   quote do
     # Use all HTML functionality (forms, tags, etc)
     use Phoenix.HTML

     # Import LiveView helpers (live_render, live_component, live_patch, etc)
     import Phoenix.LiveView.Helpers

     # Import basic rendering functionality (render, render_layout, etc)
     import Phoenix.View

     import MyappWeb.ErrorHelpers
     import MyappWeb.Gettext
     alias MyappWeb.Router.Helpers, as: Routes
   end
 end

 @doc """
 When used, dispatch to the appropriate controller/view/etc.
 """
 defmacro __using__(which) when is_atom(which) do
   apply(__MODULE__, which, [])
 end
end

Finally here is the template it is rendered within the general app template,

<%= form_for @changeset, @action,[phx_change: :validate, class: "overflow-visible", id: "responseform"], fn f -> %>

    <%=  live_render(@conn, MyappWeb.ExampleLive, session: %{"response" => @response.response,}) %>

    <%= render(MyappWeb.SharedView, "checkbox.html") %>
    <%= submit %>
<% end %>



PS the form is rendered in a regular template with a regular controller action. this setup worked in version 0.5 / 1.4.6.

live_render is called from within this regular template as shown above

PS here is other information

form a brand new toy app generated with --live

mix phx.new toy --live

the phx-change event is fired from a live route such as one generated with

mix phx.gen.live Accounts User users name:string age:integer  

but not from a regular route

mix phx.gen.html Responses Response responses name:string q1:string

here is the form containing a live_render

<%=
form_for @changeset,
@action,
[phx_change: :validate, class: "overflow-visible", id: "responseform"],
fn f -> %>
  <%= if @changeset.action do %>
    <div class="alert alert-danger">
      <p>Oops, something went wrong! Please check the errors below.</p>
    </div>
  <% end %>

  <%= label f, :name %>
  <%= text_input f, :name %>
  <%= error_tag f, :name %>

  <%= label f, :q1 %>
  <%= text_input f, :q1 %>
  <%= error_tag f, :q1 %>

  <%= label f, :q2 %>
  <%= text_input f, :q2 %>
  <%= error_tag f, :q2 %>

  <%=  live_render(@conn, ToyWeb.QmyLive,
      session: %{
          "response" => @response
      }
  )
  %>
  <div>
    <%= submit "Save" %>
  </div>
<% end %>

all necessary modules are there it compiles ok, it is “live” connected but the phx-change event never fires.

Can you also share the LiveView that renders the code with the form?

here is the live view

defmodule ToyWeb.QmyLive do
  use Phoenix.LiveView

  require Logger

  def mount(_params, session, socket) do
    IO.inspect(connected?(socket), label: "CONNECTION STATUS")
    {
      :ok,
      assign(
        socket,
        %{
          "labels" => "toto",
          "response" => session["response"]
        }
      )
    }
  end

  def handle_event("validate", value, socket) do

    IO.inspect(connected?(socket), label: "HANDLE_PHX_CHANGE")
    newval = value["response"]

    {:noreply, assign(socket, response: newval, labels: "#{:rand.uniform(25)} tutu" )}

  end
  def render(%{
        "labels" => labels,
        "response" => response,
             }) do

    IO.inspect(true, label: "HANDLE_RENDER")
    Phoenix.View.render ToyWeb.QmyView,
      "qmy.html",
      response: response,
      labels: labels
  end

end

here is the view module

defmodule ToyWeb.QmyView do
  use ToyWeb, :view
end

here is the template in templates/qmy/qmy.html



<hr>

<%= @labels%>

<br>
<%= inspect @response %>


The LiveView that renders this:

<%=
form_for @changeset,
@action,
[phx_change: :validate, class: "overflow-visible", id: "responseform"],
fn f -> %>
  <%= if @changeset.action do %>
    <div class="alert alert-danger">
      <p>Oops, something went wrong! Please check the errors below.</p>
    </div>
  <% end %>

  <%= label f, :name %>
  <%= text_input f, :name %>
  <%= error_tag f, :name %>

  <%= label f, :q1 %>
  <%= text_input f, :q1 %>
  <%= error_tag f, :q1 %>

  <%= label f, :q2 %>
  <%= text_input f, :q2 %>
  <%= error_tag f, :q2 %>

  <%=  live_render(@conn, ToyWeb.QmyLive,
      session: %{
          "response" => @response
      }
  )
  %>
  <div>
    <%= submit "Save" %>
  </div>
<% end %>

precisely, it is not a live route it is a regular edit action that renders a form template that contains a live_render instruction
for completeness here is the edit template

<h1>Edit Response</h1>

<%= render "form.html", Map.put(assigns, :action, Routes.response_path(@conn, :update, @response)) %>

<span><%= link "Back", to: Routes.response_path(@conn, :index) %></span>

Please remove the action from the form and set it to “#”

<%=
form_for @changeset,
“#”,
[phx_change: :validate, class: “overflow-visible”, id: “responseform”],
fn f → %>

I get this error message

and my intent is to keep the regular save behaviour, I am using the validate live change only, not the live save

no function clause matching in Plug.CSRFProtection.get_csrf_token_for/1

  ** (FunctionClauseError) no function clause matching in Plug.CSRFProtection.get_csrf_token_for/1
        (plug 1.10.0) lib/plug/csrf_protection.ex:237: Plug.CSRFProtection.get_csrf_token_for('#')
        (phoenix_html 2.14.1) lib/phoenix_html/tag.ex:241: Phoenix.HTML.Tag.csrf_token_tag/3
        (phoenix_html 2.14.1) lib/phoenix_html/tag.ex:204: Phoenix.HTML.Tag.form_tag/2
        (phoenix_html 2.14.1) lib/phoenix_html/form.ex:372: Phoenix.HTML.Form.form_for/4
        (toy 0.1.0) lib/toy_web/templates/response/form.html.eex:2: ToyWeb.ResponseView."form.html"/1
        (yoy 0.1.0) lib/toy_web/templates/response/edit.html.eex:3: ToyWeb.ResponseView."edit.html"/1
        (phoenix 1.5.1) lib/phoenix/view.ex:309: Phoenix.View.render_within/3
        (phoenix 1.5.1) lib/phoenix/controller.ex:788: Phoenix.Controller.render_with_layouts/4
        (phoenix 1.5.1) lib/phoenix/controller.ex:776: Phoenix.Controller.render_and_send/4

phx_change event is only a thing within the context of a LiveView as far as I know. I understand that in an early version of LiveView the change event was fired and you were able to handle it in an inner LiveView, but I don’t know if this was an intended use case.

Does it mean to trigger a live view or live component one must always use a live route at the very beginning ?

if so, is it still possible to mix live views and views calling llive views and regular views and intermixing templates ?

I have observed that for some people (maybe under a firewall or strict corporate access to Internet) the live behaviour hangs so that it appeared safer to ensure that a regular save of the form still works in case the live behaviour was frozen. is this still achievable if the request starts by a live route ?

You can live_render from a regular controller or a normal template as well.

so that means I need to move the form tag carrying the phx_change attribute of my regular template inside the inner live view leex template but then more data need to be passed through the session.

what was wrong with the old behaviour ?