Qqwy

Qqwy

TypeCheck Core Team

LiveView: shallow states or deep states?

I’ve been building some projects using Phoenix LiveView, and have read the source code of quite a few more projects made by other people (thanks, Phoenix Phrenzy contest!).

One thing I am seeing, is essentially a dichotomy in two approaches to working with state and interactivity, when working with LiveView-powered applications that are essentially ‘single-page’ (i.e. rather than one or multiple pages with multiple independent LiveView snippets, the whole interface is driven by LiveView).

Approach 1: “Shallow State”

In this approach, we have many nearly-independent LiveViews/ (stateful) LiveComponents that mostly work with their own socket.assigns directly.

Because socket.assigns is a map, and because all of these components are stateful, the data that is stored and the communication between them is mostly ad-hoc, i.e. non-normalized.

Approach 2: “Deep State”

In this approach, we have a single LiveView with potentially multiple nested (stateless) LiveComponents that contains the state in a (deeply) nested map in its socket.assigns. Most or all changes that come in travel through this single LiveView’s handle_event and potentially update (some nested part of) its socket.assigns. The socket.assigns consists of one or a few Elixir Structs, which makes it very clear what kind of data to expect, i.e. the data is normalized.

Using this approach, it is possible to extract most of the domain-logic out to its own separate module(s), making the code easy to test. Of course, you do end up with more code since you add a/some layer(s) of abstraction between the LiveView-module and the place where the actual state-transition logic happens.

This kind of architecture has strong similarities with first-order functional reactive programming (also known as ‘the Elm Architecture’(TEA)).


Now, I don’t know which approach is better suited for what kind of situations, but I find it very interesting to see that there are these two sort-of opposed kinds of working with LiveView going on, and I’d love to start a discussion on the pros and cons! :grinning:

First Post! Switch mode

Qqwy

Qqwy

TypeCheck Core Team

Hi everyone! Are there more people who have given this some thought, or am I too early with this discussion? :upside_down_face:

Let me just ask you, then: In what way do you manage state in your LiveView application? :grin:

Most Liked

Qqwy

Qqwy

TypeCheck Core Team

During @chrismccord’s Keynote-talk at ElixirConf.EU V yesterday he told us about the new changes and additions that are happening to LiveView.

One important thing he mentioned, is that there will be better diffing-support for deeply-nested states. Previous versions of the LiveView assigns-diffing would not be able to look deeper into the keys of nested maps (and especially inside structs), meaning that code like this:

<div>
  <span> <%= @post.author %> </span>
  <p> <%= @post.content %> </p>
</div>

would be re-evaluated as a whole every time one of the fields inside assigns.post would change. This is now (in the newest version on master; the latest kinks were ironed out during these last couple of days) no longer the case.


So the tl;dr: Deep states will be much better supported going forward, which means that we as developers have more flexibility in the approach we take.

chrismccord

chrismccord

Creator of Phoenix

Deep diffing shipped many weeks ago :slight_smile:

mbuhot

mbuhot

I’m just learning LiveView through the PragStudio course which is excellent so far.

The temporary_assigns feature makes it feel like the framework is pushing users towards the shallow state style. Can you indicate that a nested map key is a temporary?

I found it difficult to track which assigns are available within the template without some declaration of the data structure, so I’ve adopted a convention of an inner Assigns module within the LiveView module that declares a @type and provides functions for managing the state:

Example
defmodule MyApp.MyViewLive do
  use MyApp, :live_view

  alias MyApp.SomeContext
  alias MyApp.SomeContext.SomeSchema

  defmodule Assigns do
    @type t :: %{
            items: [SomeSchema.t()],
            filter: String.t()
          }

    @spec new([SomeSchema.t()]) :: t()
    def new(initial_data) do
      %{
        items = initial_data
        filter = ""
      }
    end

    def temporaries() do
      [items: []]
    end

    def with_filters(socket, items, filter) do
      socket |> assign(items: items, filter: filter)
    end
  end

  @impl true
  def mount(_params, _session, socket) do
    initial_data = SomeContext.get_some_data()
    socket = assign(socket, Assigns.new(initial_data))
    {:ok, socket, temporary_assigns: Assigns.temporaries()}
  end

  def render(assigns) do
     ~L""" access @items etc here """
  end

  def handle_event("some_event", %{"filter" => filter}, socket) do
    data = SomeContext.get_some_data(filter)
    socket = socket |> Assigns.with_filters(data, filter)
    {:noreply, socket}
  end
end

For the relatively small examples I’ve followed so far, this has been helpful to allow me to treat the Socket.assigns map as a data structure with well defined operations. Looking forward to some more complex examples with Components :slight_smile:

Last Post!

sodapopcan

sodapopcan

I’ve been thinking about this a lot over the past few days and was happy to find this question but a bit disappointed in the lack of discussion here. Maybe I’m not looking far enough back through the forum (very possible).

Thank you @Qqwy for making me aware of the Phoenix Phrenzy contest! This is super helpful as I’m just working on my first substantial project in Phoenix with LiveView.

I’ve been working with nested state which is nice with components but then ran into the temporary_assigns issues @mbuhot pointed out. Is anyone aware of any way to achieve this with nested state or it’s just plain impossible right now?

Regardless, I’m likely going to be converting to flat state when I get back to it tonight. My desire to nest was largely due to having a noisy bloated mount/3 and I was able to write a query with preloads to get all my initial state in one call. But then I found this solution in the winning submission to Phoenix Phrenzy which I find compelling: georacer/lib/geo_racer_web/views/race/view_model.ex at 6a54ed21f3a9705da72fc92699a5932b1071f798 · zkayser/georacer · GitHub

Where Next?

Trending in Discussions Top

AstonJ
As the title says, please share what you’ve been up to with Elixir. Whether that’s been learning it, looking into it, making stuff with i...
2976 91332 914
New
byu
@chrismccord : I just saw the Extract AGENTS.md from Phoenix.new into phx.new generator commit to the phoenix project. My initial shotgu...
New
arcanemachine
I was working on an Ecto migration and I needed a timestamp. So, for the nth time, I looked up the different data types for timestamps, a...
New
AstonJ
Just a general thread to post chat/news/info relating to AI/ML stuff that may be relevant for Nx now or in the future. Got anything to sh...
New
type1fool
I just stumbled on a newly redesigned elixir-lang.org. :tada: It looks like @Software_Mansion did the work, and I think it is generally a...
New
juhalehtonen
There has been a thread to discuss the Stack Overflow Developer Survey on this forum every year since 2018, so here’s yet another one for...
New
alexslade
Fly’s CEO posted this recently - Turn And Face The Strange · The Fly Blog It says that Fly is going all-in on sprites, which is a worry ...
New

Other Trending Topics Top

JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge &amp; Solve. They are GUI (Emerge) and State management (S...
New
ausimian
Emily is an Elixir library that runs Nx computations on Apple’s MLX. Install it as the default Nx backend and Nx, defn, Axon, Nx.Serving,...
New
akoutmos
@hugobarauna and I (Alex Koutmos) have been hard at work on writing a book on Nerves that takes you from simply blinking LEDs to building...
New
bjorng
We want to introduce a new native datatype to Erlang: native records. Although replacing all tuple records with native records is not our...
New

We're in Beta

About us Mission Statement