Passing Structs as attr in LiveView component

I am stumped on an issue I am seeing with Phoenix LiveView. I have the following phoenix component:

attr :user, MyApp.User, required: true

def my_component(assigns) do
~H"""
    <h1> Hi, <%= @user.first_name %> </h1>
"""
end

and I invoke it passing in my Ecto User struct like so:

defmodule MyApp.MyLive do

use MyAppWeb, :live_view

    def render(assigns) do
        ~H"""
        <div>
            <.my_component user={Repo.get!(User, 1) />
        </div> 
        """
    end
end

Now when I try to render this live view I get the error protocol Phoenix.HTML.Safe not implemented for %MyApp.User{} ....

This leads me to two questions:

  1. Why does my struct need to implement the Safe protocol
  2. The LiveView docs for attr state: Required struct types are annotated and emit compilation warnings. For example, if you specify attr :user, User, required: true and then you write @user.non_valid_field in your template, a warning will be emitted. This seems at least lacking additional instruction if you can’t do that without implementing the Safe protocol first.

You need a . to invoke function components:

<.my_component user={Repo.get!(User, 1)} />

You probably want to assign the user outside of render and pass that assign instead because functions in templates do not play well with change tracking.

If your my_component definition lives in a module that is not imported, you must fully qualify it with:

<MyModule.my_component user={@user} />
2 Likes

Also this is not the correct syntax, it should be…

<h1> Hi, <%= @user.first_name %></h1>
1 Like

Good catch. I updated the code samples above. And I have the components being imported. Questions 1 & 2 still stand.

That Phoenix.HTML.Safe error is just what you get when you try and cast a value into HTML. If your original examples were how you were doing it, it’s just because HEEx was trying to cast the literal struct %User{} into valid HTML (essentially just a string) which it couldn’t. You would get the same error if you did something like:

~H"<%= %Ecto.Multi{} %>"

So if you initially did have <my_component user={Repo.get!(User, 1) /> without the ., then the user={} was trying to cast a %User{} into a valid HTML string. You could implement the protocol for these structs if you wanted to… it’s basically the same as to_string methods in OO, but there is no need to and I wouldn’t really recommend it.

Thanks soda! Sorry I should have been more clear. I coded up a sample that captured my use case and just forgot the . in that, not in my actual project. That makes sense what Phoenix.HTML.Safe is now. I still don’t really understand why the user struct is being cast to HTML. I can start a brand new LiveView project and put it on git if that would be helpful.

oh my god. Upon looking at my code again I realized I did, in fact, leave off the . on my LiveView Component. Once I fixed that it worked. Thank you!!

1 Like