Setting assigns for layout-level component is shadowed by ... something?

What is defeating me today:

I want a component, included from a layout template, to have access to conn and/or assigns

What I am doing:

  1. Add a plug to the browser pipeline that takes information from the conn session and puts it into the assigns map.
    user = get_session(conn, :current_user)
    conn = assign(conn, :avatar, user.avatar)
    conn = assign(conn, :userid, user.id)

So far so good; if I then access this from a controller-driven template, we’re happy campers.

However, in components included from a layout template, assigns does NOT contain these values, and instead contains only %{__changed__: nil}

Sequentially, via logging, I can see that:

  1. The plug runs, and assigns the values, which are viewable in the conn struct.
  2. They layout component renders, but assigns at this point has the __changed__ value only
  3. The controller renders, at which point assigns is back to what I expected it to be.

So: something is shadowing assigns for the layout/component sequence? Where does that come from?

I presume this is a bug in my code, but I am not even sure where to look for it.

UPDATE – none of this is for Live View; this is all layout / controller driven content. Much of the prior conversations I have been able to find pertain to debugging live view interactions.

UPDATE – so, I have come to understand that “assigns” for a component is only what is directly passed in from the parent template:

     <.account_widget />

Which is implemented by a module with method:

  def account_widget(assigns) do

    ~H"""
    <div><%= @avatar %></div>
    """
  end

Only gets paramaters passed into it from the dot-notation, which in this case does not include @avatar – even though that data IS on the assigns portion of the conn struct.

So!

I can either pass the specific data I want down the “call stack” of template inclusion, from the point where it IS available – UGLY! –

OR

I can obtain the conn object from the component module? Is this possible? It certainly seems like it should be, but it’s not obvious how. Simply importing Plug.Conn does not seem sufficient. Thoughts on best practice here? This doesn’t seem like it should be such a difficult scenario, but other than doing the ugly thing, I’m a bit baffled.

UPDATE #3

So, I sort of successfully implemented the ugly approach, but it gets uglier when an attribute is not available: passing a nil value down the inclusion chain does not pass the compiler test.

Some of the components in the chain are pure templates, no explicit module in which to handle alternate attribute values or set defaults.

So, not only does it make the component NOT self contained, but it would force all kinds of hinky logic up the inclusion chain.

There MUST be a way to access the conn struct from a component! Or else I am really using this framework in the wrong way.

This is the way.

My guess is there might be some misconceptions here - so I’m glad you’ve posted on the forum!

You probably don’t want to access the conn struct from the component. Just give it the minimal data it needs to render.

<.account_widget avatar={@avatar} />

Well, having gone through the api/function docs more closely, I came to the conclusion that y’all are telling me and ended my implementation there.

I don’t love it as an answer, as it means that components cannot be nearly as independently self-contained as one might want in many use cases, and with nested components, it means extra passing-down-the-stack of stuff that could have been independently obtained.

But that is probably my new-ness to the framework showing, and in due course I shall see the overall elegance of such a limitation.

In any case… thanks for the help & confirmation!

I understand the feeling. I think there can be some adjustment required if you’re coming from different frameworks. But stick with it if you can. Hopefully it will start to make sense and you’ll come to love it :grin:

Hypothetically you could just pass the whole conn struct around everywhere and each component consumes it and does whatever it wants. The drawback I find with that is that at some point you might change what’s in the conn, or how it’s structured, and then a bunch of components all over the place break.

You’re probably already aware of it but I have found it useful to deal with nil values by conditionally rendering a component with the :if directive. For example, if the account widget should only render if the @avatar value is available:

<.account_widget avatar={@avatar} :if={assigns[:avatar]} />
1 Like

Thanks for the additional tips @msmithstubbs Matt. Absolutely: I prefer to use a framework as it is intended to be used. Makes everyone’s lives easier.

I’m particularly grateful for the tip on :if – I have used that on standard elements, and (doh) it didn’t occur to me it could also be used on components. Ultimately not what I want to do in this instance, but definitely a useful tool!