When to use a live component, instead of a functional component?

Alex Korban recently wrote an article called “Thoughts on Elixir, Phoenix and LiveView after 18 months of commercial use”, where he mentions that he avoids using live components whenever possible.

Live components are best avoided if possible, in my view.

There is a discussion of his article on Hacker News. Here Jose Valim agrees with him on his point about live components.

You are 100% correct on functional components vs live components. I will make it clearer in the docs the former should be preferred

The live component docs indeed include a short section about when to use functional components or live components.

Generally speaking, you should prefer functional components over live components, as they are a simpler abstraction, with a smaller surface area. The use case for live components only arises when there is a need for encapsulating both event handling and additional state.

However, I still don’t fully understand the rational behind choosing the one or the other.

For example, what does it mean for something to have a smaller surface area, in the context? And why is smaller better?

And why should live components exclusively be used when there is a need for event handling AND additional state?

What are the (hidden) costs of using live components, that make them something to avoid?

One reason I have been used live components is to make my code simpler to understand and easier to reason about, by encapsulation event handling and state. However, in some cases my code did not improve as much as I expected in that regard, partly because of the extra message handling between the root live view and the live component.

9 Likes

Where things splinter is when you want to use PubSub. That relies on handle_info callbacks, which are so far only available close to the process or LiveView.

For a simple count badge, it was easier for me to use a functional component the LiveView handled state for. It ended up being 3 separate counts but I had less mental load with less oddities. My mental model for a badge counter is to init the record count from the database then use PubSub to increment that count.

I had problems understanding how to do that with update of LiveComponent in the mix. I wanted the state of each count with the component because colocating that state made sense. Marshalling messages through handle_info made less sense to me but I may be missing the plot on that.

I would say there is a place for a cheatsheet, checklist, or flow chart diagram that’s like “If you want PubSub, you try X with Y.”

3 Likes

I have this SQL query builder component. At some point I wanted to use it on a couple of other pages. It uses url query params and all the events were handled in the liveview. I went with keeping it as a function component and adding the params and event handling to the parent liveviews with attach_hook. This kept all the behavior in a module and there is only one callback to implement in the module that uses it.

Pretty sure I looked into doing it as a live component but as you said, you end up having to send events back and forth and it can get out of hand for some use cases. It is especially pointless when there is no internal state or events.

Say you’ve got a calendar component. It can be in either day, week or month view, has to accept events for navigating back and forwards through time. Most of the events and state are internal. The parent only cares about the date picked. This can be a live component and the parent handles one event and passes one assign. The rest are internal. You’re unlikely to have more than one or two on a page so the overhead isn’t much of a worry.

4 Likes

Thank you. That is very helpful.

Agree, if you don’t need internal self-contained state, using a live component is not recommended.

We were using it with the eariler versions of liveview, as there was no other way to do things back then.

This is helping me thinking about code organization better. I found an example in the codebase of LiveBook of this approach (see the LivebookWeb.Confirm module). Potentially a game changer for me personally. I did not realize the full power of hooks

1 Like

The use case for live components only arises when there is a need for encapsulating both event handling and additional state

This sounds like you could still do event handling EOR additional state w/o live components.

Does this mean that you handle events in functional component? Or rather that you handle them in whatever uses functional component(s) and update those from there?

Would like to make sure I understand correctly “both event handling and additional state”, with emphasis on BOTH and AND.

Also – is there any difference between “function component” and “functional component” in Phoenix parlance?

I think not, just spelling. Function/functional components are functions that take a single argument assigns map and return a HEEx template:

def my_component(assigns) do
  ~H"""
  <h1>hello</h1>
  """
end

The latter. The former is not possible, as those are only functions. But events (in a LiveView or LiveComponent module) can mutate the assigns and change tracking will take care of updating whatever state changed in the function component, send the diff to the client and patch the DOM.

The emphasis on both is to guide people not to overreach to a more complicated solution. LCs have their space for organizing “too complex” LVs and for optimizing diff payloads. But they also add complexity, force you (and team) into understanding more pieces and how they interact.

If you only need to handle events, you can do so in a LV. If you only need to build reusable components that render based on assigns, that’s what FC help you compose.

1 Like