My experience after my LiveView 101 application

Foreword

These were my notes after doing my first 101 LiveView application that was mostly CRUD, shopping list. This is not a criticism to LV but observations and most probably majority of them caused by my inexperience.

Some of them could be solved by external JS tools also but I purposefully didn’t include anything external to see the full power and limitations of LV.

I wanted to share with you, learn from them and hopefully contribute to the community to overcome some of the challenges I have faced or help anyone who is going through the same journey.

When I say wire, I mean the connection and payload, with LV the diff and events that is being sent and received from the client.

Select all checkboxes:

Fairly simple thing in the frontend and optimized payload with standard HTML forms. I have seen solutions to keep track of them on the socket, in order to keep things in sync. You kind of have to keep them in the socket otherwise your checkbox is checked on the frontend for something that is not checked in the socket.

Eliminates temporary_assigns comes with performance and wire drawbacks as the list grows.

You can’t have form input with multiple submit buttons

You would like the add notes and set the status of an item via different submit buttons. I have searched around and it is not possible with pure LV.

You can’t capture the value of submit button or the input field(s) if you assign it to phx-click. If you wrap it in a form then LV won’t be able to track the diff and send the whole form payload every time as well as you would still not know which submit button was pressed.

To memory or to wire
Either sacrifice memory or wire. assign or temporary assign is the problem. Both come with pitfalls.

If you assign, a simple add to favorites can be penalizing and a jarring user experience! In my demo I order the favorites at the top, It is an awful user experience when things move underneath your mouse and your wire loads 100s of entries because you changed something in one of them. You can solve one of them by temporary_assigns and update the entries in the socket with phx-update directive in the html.

Temporary assigns and updating funkiness for delete
You have to do funky stuff with updating to delete things with a minimal wire transfer like
if item.__state__.deleted == true, do: "is-row-hidden"

Form resetting
If you use changesets for your form, your parameters and changesets are not bound since it is a one-way journey, unlike the normal HTML

If you would like to reset your form on successful submission without hacky javascript, you would have to have sacrifice wire and add phx-change on your forms to validate changesets.

If you do not use changesets, same problem still occurs. I ended up flip-flopping between nil and "" on successful form submission.

If you want to preload, cache something in your component once ie: categories for the form…
That has parameter dependency as mount will get called without any of the parameters you passed.

live_component @socket, MyForm, user: @user

MyForm
mount(socket) 
no user to fetch categories for the given user scope :(

The update will be called with parameters but in every lifecycle.

I ended up moving the logic to LV and sending it as a parameter, which meant self encapsulated logic from the component had to bleed into LV.

LC can’t broadcast
I was surprised and searched around, didn’t find anything. I had to send it to LV again, resulting in logic bleeding out to LV again.

Multiple LVs on one page
I have read on the forums people using multiple LVs on the same page but haven’t tried that setup yet. This was more of a 101 attempt for me and that pattern might alleviate some of the problems but brings the question the power of LC, just neat rendering blocks and all the other stuff you use LV?

1 Like

I gave up all stateful components. My current thinking is the idea of self-encapsulated logic is too OO-ish and don’t fit well with the functional philosophy of Elixir. Yes, My LV module is bloated but there are other ways to alleviate, such as using defdelegate to delegate to other modules.

1 Like

Well modules to fit very nicely with elixir so why LC shouldn’t be able fit somewhat.

Could you share pseudo code for that approach?

YMMV, my point is LV offers you many weapons, and you don’t have to use all of them. I am just another person with a couple of months of experience on LV under my belt. What I found useful for me is to stay within a minimal set that is both 1, functionally complete and 2, I feel comfortable with. My minimal set is:

  • Only one LV in my application. I don’t even use live_redirect. I use live_patch and handle_params/3 extensively.
  • No stateful components. I use state-less components extensively.

I have tons of data assigns and clauses of handle_event/3 in my LV module. I refactor code for any clause that has more than a couple of lines to another function. Once I have a few functions that are logically related I made a separate module.

2 Likes

See if this blog post can help you:

A stateful component is denoted by specifying an :id in the live_component/3 assigns. Otherwise, it is considered a stateless component. The actual id is the combination of the component module name and the :id field, so you can have duplicate ids across different types of components.

Stateful components can handle events and modify their own state. Meaning that they can be re-rendered independent of their parent LiveView.

Stateful Component Preload

If you need to render multiple components of the same type and it would be beneficial to initialize them all at once, you can do so by defining a preload/1 function in the stateful component. This could be useful, for example, if you would like to get the components’ state from a database in a single query, rather than a separate query for each component, avoiding the N+1 query problem.

The preload/1 function is called before the components’ update/2 functions. It receives a list of the component assigns and maps those to a new set of assigns for each component, which will receive the mapped assigns in their update/2 functions. In the process, the preload/1 function could create new assigns that were not in the original live_component/3 assigns keyword list.

That example makes it sounds like preload executes once but not according to the official documentation. I haven’t tested it though, could try to test it.

on first render, the following callbacks will be invoked:

preload(list_of_assigns) -> mount(socket) -> update(assigns, socket) -> render(assigns)

On subsequent renders, these callbacks will be invoked:

preload(list_of_assigns) -> update(assigns, socket) -> render(assigns)

https://hexdocs.pm/phoenix_live_view/Phoenix.LiveComponent.html#module-stateful-components-life-cycle

2 Likes