Liveview 0.18.3 bug on component?

here is the setup

Entity has a field fied1 defined in the schema and also a belongs_to User, that is not loaded

def Entity do
schema “entities” do
field :field1, :string
belongs_to :user, User

timestamps()

end
end

on the other file where the component is defined

alias …Entity

attr :entity, Entity, default; %Entity{}

def show(assigns) do
~H"“”
<%= @entity.field1 %> triggers an error key :field1 not found in: ecto.Association.NotLoaded

<%= Map.get(@entity, :field1) %> works ok
“”"

the component is called as <Comp.show entity={@entity} />

1 Like

I would IO.inspect(@entity). It looks like you are passing in an association that isn’t loaded.

1 Like

I am sure the entity is ok I inspected it
it does have an association not loaded, but it is not concerned by the field field1
it should not fail since Map.get is ok

Map.get and .field1 aren’t the same thing. Map.get(map, key) returns nil if the key does not exist. dot syntax raises:

iex(1)> %{foo: 1}.bar
** (KeyError) key :bar not found in: %{foo: 1}

iex(1)> Map.get(%{foo: 1}, :bar)
nil
1 Like

yes but it should not say association not loaded in any case on a field that is declared in the struct and that is not related to an associated other entity
here Map.get(@entity, :field1) gives the value, not nil
so why does @entity.field1 does not give the same value but instead association not loaded

this is in the context of the Phoenix component, the same thing outside works ok

What seems to no be loaded, is entity itself. Sounds like entity comes from a related lookup where entity wasn’t preloaded.

2 Likes

the component is called like this

<%= inspect @row.entity %>
< comp entity={@row.entity] />

in the inspect line all is ok but inside the component the problems shows up
the entity is here and it does contain a not loaded associated field
but it is not this one that is called inside the component

it looks like through the component there is a mismatch

Try adding in your Ecto query preload: [:entity].

https://hexdocs.pm/ecto/Ecto.Query.html#preload/3

Honestly without more code this is hard to diagnose. What I can say for sure is that this is not a bug. Phoenix live components don’t load or change data, so this isn’t relevant.

If you can show us your actual code without redactions, and give us full files with the full call stack, we can probably find the bug. Without that we’re just gonna have to guess.

1 Like

I have set up a new phoenix minimum app that shows the bug , at disposal for who want to run on its own machine

there is a bare live view, phx generated, for “rows” with an onmount callback for index and show
a user has one parameter

the component is called only from the show live view, not the index one

in show.html.heex

<ContactComponent.display row={@row} parameter={@current_user.parameter}/>
defmodule Bg1Web.AssignUser do

  alias Bg1.Accounts
  import Phoenix.Component

  def on_mount(:default, _params, session, socket) do
    {
      :cont,
      socket
      |> assign_new(
        :current_user,
        fn ->
          Accounts.get_user_by_session_token_with_parameter(session["user_token"])
        end
      )
    }
  end
end

in accounts.ex

  def get_user_by_session_token_with_parameter(token) do
     get_user_by_session_token(token)|> Repo.preload(:parameter)
  end
defmodule Bg1Web.RowLive.Index do
  use Bg1Web, :live_view

  alias Bg1.Rows
  alias Bg1.Rows.Row

  on_mount Bg1Web.AssignUser
  @impl true
  def mount(_params, _session, socket) do
    {:ok, assign(socket, :rows, list_rows())}
  end
..... rest of mix generated live module
defmodule Bg1Web.RowLive.Show do
  use Bg1Web, :live_view

  alias Bg1.Rows

  alias Bg1Web.ContactComponent
  on_mount Bg1Web.AssignUser
  @impl true
  def mount(_params, _session, socket) do
    {:ok, socket}
  end
..... rest of mix generated live module

the index page of rows shows that the current_user has its “has one” parameter loaded

if one clicks to the live redirect link, its ok

<%= live_redirect “live redirect Show”, to: Routes.row_show_path(@socket, :show, row) %>

the result is
row1b

now if one clicks the full reload link

<a href={"/rows/#{row.id}"}>full reload link</a>

then the bug shows up, so it has to do with assign_new

now from show page, full page reload, call to component commented out

inspect @current_user.parameter shows it’s here

now remove comment for component, full page reload, and same if restart server

(note: reformatted your post to use ``` to delimit code examples)

I suspect this is the issue - is there someplace else in your application that is setting :current_user in assigns before this runs? In particular, a place that calls get_user_by_session_token(token) but does not preload parameter?

yes when the user logs in current_user is in conn

but that normally does not translates automatically to socket in mount
indeed if I remove on_mount assign user, @current_user does not exist in the liveview

so in the liveview, parameter is preloaded as seen in the screenshots

Thanks for showing so much code. Given that it’s a dedicated example, can you put this all on github so that I can run it?

here is the repo GitHub - blset/bg1

register a user
create a row
create a parameter for the user id
got to index or show row

In your AssignUser.on_mount, during the disconnected rendering that happens first, the assign_new uses the :current_user set in the Plug pipeline by UserAuth.fetch_current_user (see doc). This uses Accounts.get_user_by_session_token that doesn’t have the preload you want so it crashes.

If you navigate to this page while being already connected on the websocket, it can’t reuse the :current_user so it has to fetch it via Accounts.get_user_by_session_token_with_parameter that has the good preload so it works.

1 Like

ok that explains,

In some cases, there may be data that is shared by your Plug pipelines and your LiveView, such as the :current_user assign

Finally, is there other cases of shared data between Plug and LiveView besides :current_user case ?

where is this programmed in liveview may be interesting to understand other cases.

By using assign_new in the mount callback of your LiveView, you can instruct LiveView to re-use any assigns set in your Plug pipelines as part of Plug.Conn, avoiding sending additional queries to the database. Imagine you have a Plug that does:

it’s not clear anymore when does this happen.

for my case the reason of error is now clear,
for pull page reload:
on first render :current_user comes from Plug and so the components in the page gives an error, this if finally not
because assign_new is not triggered, because assign_new is triggered on second render

on second render, assign_new is triggered, since without the component the page shows current_user with preload (and with the component there is no second render because of error in first render).

so current_user is not shared between conn and socket, it’s just a bug in first render

for live_redirect, :current_user with preload is already there, so no problem with the component

so what does it mean that data is shared with conn ? it looks like there is no implicit share, and one has to resource for instance to session to pass variables

more precisely the share would be between a parent live view and a nested liveview, because this is written in paragraph Referencing parent assigns Phoenix.Component — Phoenix LiveView v0.18.3

but under what circumstances with conn, then ?