Passing values to nested function components

I’m struggling with passing values to nested function components.

My feeling was that passing ‘user’ from the parent to the child as shown below would work.

However it doesn’t and the compiler complains that variable ‘user’ is undefined.

defmodule MyAppWeb.Playground.Nested do
  use MyAppWebWeb, :live_view

  @impl true
  def mount(_params, _session, socket) do
    IO.inspect(socket.assigns.current_user)
    {:ok, socket}
  end

  @impl true
  def render(assigns) do
    ~H"""
    <div class="flex items-center justify-center">
      <div>
        <p>Main container</p>
        <.parent user={@current_user}>
          <:child let={user}>
            <.child />
          </:child>
        </.parent>
      </div>
    </div>
    """
  end

  slot :child, required: true
  attr :user, :map, required: true

  def parent(assigns) do
    ~H"""
    <div>
      Parent <pre><%= inspect(@user, pretty: true) %></pre>
      <%= render_slot(@child, @user) %>
    </div>
    """
  end

  attr :user, :map, required: true

  def child(assigns) do
    ~H"""
    <div>
      Child <pre><%= inspect(@user, pretty: true) %></pre>
    </div>
    """
  end
end

The docs seem to only use one example - a table with multiple rows and columns for which a for expression is used.

I’m sure I am not grokking something simple as we should easily be able to compose complex UIs from composing simple function components.

I’d be very grateful for any pointers.

Thanks.

 <.parent user={@current_user}>
  <:child let={user}>
    <.child user={user} />
  </:child>
</.parent>

You need to pass the user to .child. let just gives you a place to bind variables, you still need to use them explicitly.

Thanks for the reply @LostKobrakai , that makes sense. However, after making the change to:

 <.parent user={@current_user}>
  <:child let={user}>
    <.child user={user} />
  </:child>
</.parent>

I still get the error :

error: undefined variable "user"

for both

 <:child let={user}>

and

The full example is now :

defmodule MyAppWeb.Playground.Nested do
  use MyAppWeb, :live_view

  @impl true
  def mount(_params, _session, socket) do
    IO.inspect(socket.assigns.current_user)
    {:ok, socket}
  end

  @impl true
  def render(assigns) do
    ~H"""
    <div class="flex items-center justify-center">
      <div>
        <p>Main container</p>
        <.parent user={@current_user}>
          <:child let={user}>
            <.child user={user} />
          </:child>
        </.parent>
      </div>
    </div>
    """
  end

  slot :child, required: true
  attr :user, :map, required: true

  def parent(assigns) do
    ~H"""
    <div>
      Parent <pre><%= inspect(@user, pretty: true) %></pre>
      <%= render_slot(@child, @user) %>
    </div>
    """
  end

  attr :user, :map, required: true

  def child(assigns) do
    ~H"""
    <div>
      Child <pre><%= inspect(@user, pretty: true) %></pre>
    </div>
    """
  end
end

Very frustrating.

I feel it’s got to be some simple idiotic error I’m making - but for the life in my can’t see where.

Many thanks.

Yeah, didn’t notice it at first, but :let= vs let=. You need the former.

2 Likes

What hero! @LostKobrakai you are the man! I’ve been banging my head against the wall over this and am so, so grateful that you took the time to help. :smiley: :smiley: :smiley: :pray: :pray: :pray:

Hi Benjamin

I’m very grateful for your help in resolving this.

As you can probably tell, I’m not a developer - I work as a family physician and am interested in Elixir and Phoenix as tools for a couple of projects.

I’ve found the community to be wonderfully welcoming and very generous with their time.

Once again, I’m very grateful that you took the time to look at this and help.

3 Likes