LiveView and non-existent/optional values in assigns

As the app grows, so does its state. There are, however, things that are strictly optional, so dealing with them becomes either awkward, or unnecessary, or redundant.

This is bad, because “Generally speaking, avoid accessing variables inside LiveViews, as code that access variables is always executed on every render. This also applies to the assigns variable.” (see docs). That is, this code breaks change tracking.

<%= if assigns[:optional_data] != nil do %>
  Now we can access <%= @optional_data %>
<%end %>

A solution to this is to always set all data on mount to nil:

def mount(%{"id" => id}, _session, socket) do
 assign(
        socket,
        a_lot: ...,
        of_required: ,
        data: ...,
        already: ...,
        and_then: ...,
        optional_data: nil
      )
end

I personally don’t like this because there’s already a lot of data in the state, and adding more and more to it even if it’s optional… well, it sort of rubs me in the wrong way :smiley:

Is there a good way to do a @optional_data in a way that doesn’t break on non-existent data, doesn’t require breaking chaneg tracking or filling this data on mount? besides creating a custom compiler that deals with this? :slight_smile:

1 Like

If assigns become to plenty my general next steps would be breaking functionality up into multiple pieces either at the functional level (helpers) or even at the structural level (split into components).

1 Like

This still doesn’t solve a common thing I’ve run into with what I’m slowly trying to build.

Imagine a page with multiple dynamic components that can be added or removed at whim:

Page
  Component A
  Component B
  Component C
  ...
  ...

Basically, a thing that’s common in pretty much any CMS.

There could be several external processes doing something (retrieving external data, publishing or rendering, exchanging data etc.) that communicate current progress back to the page. Then:

Page

   if there's progress for Component A
      display progress bar
      disable Component A
   display Component A

   if there's progress for Component B
      display progress bar
      disable Component B
   display Component B

  ...

All these become something like if assigns[:progress][:some_id] != nil. They are already split into separate components, but separate components cannot subscribe to PubSubs.

I guess a way around it is to just create an entry in assigns[:progress] every time a component is added.

For now the option is to make them be nested liveview processes instead of components.

Yeah, I had an inkling that was the case. Yucky when there are many components :slight_smile:

I guess in my particular case it’s okay to just look into assigns directly. And by the time this becomes an issue for me, it will probably already be solved at the LiveView level :slight_smile:

Just subscribe in the parent lv and change the properties of the component accordingly? I don’t see the problem.

Accessing assigns directly breaks change detection in LV.

So, even updating component properties would be:

%% let's assume the component accepts :progress as a parameter

<%= live_component A, :progress: assigns[:progress] %>

I use surface, which forces you to declare all data assigns before hand. Then you pass down properties level by level. I think this is a good discipline even if you don’t use surface.

See a previous answer: LiveView and non-existent/optional values in assigns - #3 by dmitriid

You’re basically left with either filling up assigns with unnecessary data or you break change tracking. In my current side-project I end up doing both depending on what I feel like today :smiley:

Data assigns can be nested data structures, and you can use pure function accessors to get the part you want then pass down the properties to lower components. Yes, you need every top level data assigns named, and no, you don’t need to fill all data structures with every detail.

There is no such thing as optional assigns as far as the template is concerned, so you need to assign the values in mount. It can be optional for the caller, but you can use assign_new to handle the caller providing or not providing

1 Like

By “optional” I meant optional from the view of the developer. As a developer I really dislike filling data for things I will need only some of the time.

I wouldn’t be surprised if by 1.0 LiveView would have a solution for this :wink:

For now, I will do one of:

  • ignore change tracking until it becomes a problem
  • fill optional data with “empty” data (usually nil)
  • create a helper function to get that data from a nested structure
1 Like

Use assign_new to fill optional data with “empty” values, for sure.

1 Like

furthermore, the only way we can do change tracking is for it to be declaratively assigned one way or the other. Once we add compile-time based declarative assigns it may be slightly more convenient, but it still would need to be specified. We can track changes for something we don’t know about :slight_smile:

1 Like

For myself I would be happy if @x would return nil for non-existent values, and this could probably be tracked.

But I know many people wouldn’t lime this magic :slight_smile:

Yeah :-1: on that for me. Sure it requires another line somewhere to set a value, but for that you get typo safety, and that is well worth it eg: <%= @usre_name %> just raises instead of being nil.

1 Like

You’ll pry my PHP error control operator from my cold dead hands :))