LiveView with complex layouts

It’s not for state management, but for message passing. There is nothing similar in React, where components would send messages to others directly.

It’s a bit like ReactContext or Redux no? The root live view “manages” router state and updates are communicated to (grand)child views via subscriptions?

Here You can send a message from everywhere, to anywhere… for example a child could send a message to the root, and vice versa.

And a message can be a state-transfer message, a notification message…

That’s why I would not compare it to Context, which is like a shared state between a hierarchy of components.

And somewhere, You can react to those messages, and change your state… à la Redux.

1 Like

It sounds like layouts will be added in future Live View, which will be great.

Meanwhile my code is setup like:

<html lang="en" class="has-navbar-fixed-top">
    <head>
        ...
        <link rel="stylesheet" href="<%= Routes.static_path(@conn,"/css/app.css" ) %>">
    </head>

    <body>
        <!-- START NAV -->
        <nav class="navbar is-info is-fixed-top">
            <div class="navbar-brand">...</div>
       ...
        <section class="hero is-dark is-fullheight-with-navbar">
                <%= render @view_module, @view_template, assigns %>
        </section>
...

Though, the title bar and such is stateless.

I ended up putting shared views such as navigation side menus and headers in live components that are rendered by each top-level live view route component. So far this has been the best working strategy since it allows for more routed live views which makes message handling and state management much less verbose than The Elm Architecture approach that I was experimenting with.

I was hesitant to take this approach because I thought re-rendering the shared components in different live views would cause flickering but I don’t think the DOM parts are actually being replaced.

For anyone looking for an example, I’ve got some code here https://github.com/rjdestigter/fireweed. I’ve been using http://surface-demo.msaraiva.io/ to build components including the Navigation component that is rendered inside each top-level live view.

3 Likes

I went with this approach as well, however, passing data between parent/child views and handling parameters got pretty messy.

So I think using templates might be a better approach as described in this post.

Quick test against your demo deploy, in Safari 14 there is flickering on the nav icons although not the text. Trying it in Chrome & Edge on a Mac, the transitions between LVs are perfectly smooth.

Perhaps Webkit is a little weird with SVGs.

The idea of using Pub/Sub leads me to the following question: when are LiveView components able to receive messages? My underlying concern is: what is the cost of using PubSub for an application having a large number of LiveViews/Components. Here are my specific questions:

  1. If a user of the website visits some route leading to the LiveView MyApp.LiveModule1.Index and this LiveView publishes a state-related message that will be needed by other LiveViews (for example the current user). Then, and only then, the user visits one of these other LiveViews for the first time: will the LiveView already exist and have processed the state-related message, or will it only start processing messages after being loaded at least once.
  2. If the answer to the previous question is that all LiveViews will receive the message without first needing to be explicitly loaded by the router, then isn’t it an issue for large scale applications having hundreds or thousands of LiveViews/Components? (such as the one we are considering building using Phoenix LiveView). Especially when message handlers are meant to immediately assign the state passed through the message (and thus claim memory). This could result in huge amounts of RAM memory being wasted, especially as the number of users grow. On the other hand, if LiveViews will not receive messages before being visited at least once before, then pub/sub is not suitable to manage global state (damn if you do, damn if you don’t sort of).

This state passing/sharing issue across components could end up being a deal breaker for us. Because apart from Pub/Sub, I do not like at all a lot of the solutions I’ve seen discussed in this thread that look very much like the fat controllers and other God-objects produced by junior object-oriented developers. I expected Elixir, Phoenix and functional programming in general to make composition clean and easy (which I feel is the case, expect for this specific problem). I am a bit worried to see that there does not seem to exist a mature solution to this problem, especially since as newcomers to Elixir we we would not have the skills and confidence required to pull out, evolve and maintain arcane clever tricks.

From this perspective, Pub/Sub would be the ideal option for us, but I have the scalability concerns I just mentioned. Maybe the answer to this simply is that processes are very lightweight in elixir so this is not an issue in practice? However this doesn’t address the fact a user of the website would claim memory in hundreds of LiveViews/Components that he may never visit during his session. The only solution I can see to this would be to manually check if the user has visited the LiveView before assigning state but my guts tell me that this may end up being quite messy to maintain (and what about checking this for non-routable LiveViews/components).

Generally speaking, having to worry about this seems to defeat one of the main benefits of composition: every component is focused on its own simple concerns so that complex solutions are built from simple parts. But if every component now has to worry about application infrastructure concerns, this does not inspire confidence in terms of the maintainability of complex systems.

I hope someone can ease these concerns because apart from that Phoenix LiveView is absolutely ideal and exciting for us, whether it is from a performance perspective, or from an Intellectual Property protection perspective. But the need to have shared state is so crucial in UI design that it would be reckless for me to bet our luck on LiveView for such an important project if there is no efficient and elegant solution to this problem, this would be like putting a time-bomb at the foundation of our project.

I used this occasion to contribute the apprehensions and thought process of an experienced teams mainly working in OOP ecosystems and looking at Elixir and Phoenix, since I believe an increasingly number of teams will start considering Phoenix and Elixir for their front-end moving forward, so hopefully this feedback can be useful to you.

@reddy, when a user visits the website a process is created. A single process per connection. The server does not start a process for every view and component per user because, as you describe, this would be wasteful, pointless, use too many resources etc.

This answers the other question, which is now “do processes that haven’t been created receive events?” Obviously, they cannot. Phoenix.PubSub messages are not kept around so that it can forward them on to future subscribers. That is not pubsub, that is more like a message queue/broker (think rabbitmq or Kafka, which won’t keep them forever either) or event sourcing (where you could load all the previous messages from a store). Pubsub just shoots message off into the abyss.

So, what do you do? In your LiveView’s mount, you load whatever data you need to populate the page and subscribe to whatever topics you need updates from.

Edit: also, components cannot receive pubsub messages, only LiveViews. You can look at the livebeats source, which solves the how do I message the navigation bar quandary rather elegantly.

6 Likes