<turbo-stream action="append" target="posts_list"><template>    <div class="postbit" id="354617" data-post-id="354617">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="MarthinL" src="/assets/icons/user-9f439610.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  MarthinL
                      <span class="op-star" title="Thread Starter">
                        <img alt="OP" class="op-star-icon" src="/assets/thread-icons/thread-icon-thread-starter-df91e872.png" />
                      </span>
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>A positive step forward, thank you.</p>
<p>For context, I’m not looking at hundreds of thousands items but hundreds of millions per regional federation server cluster. Every user has theoretical access to a substantial portion of it, but not all at once.</p>
<p>It’s largely small records but they add up quickly, about a quarter the data tracks each user’s effective access rights while the remainder is again split down the middle between structural elements (theoretically graphs but in practice usually trees with the odd loop turning it into a sort of directed graph) and the displayed informational content (not implying the rest doesn’t carry information or doesn’t impact what user see).</p>
<p>I don’t merely wish to avoid keeping all that data in server memory, it would be physically impossible. I have no alternative than to load the data for each user in segments controlled to a reasonable size, still using a starting point but limiting levels rather than rows.</p>
<p>But reality is slightly less dramatic than the picture I paint. While it’s all accurate there are natural and practical limitations in play on several levels which drastically reduce the portion of data loaded from the database and presented to each user. That’s all taken care of by the aligning how and where I store the data with reality, being aware of and able to accommodate exceptions (intra-regional references) efficiently and presenting a user interface which essentially navigates each user through the vast expanses of data using a (comparatively) tiny window or cursor which contains only enough pre-loaded data to avoid excessive delays when drilling deeper or backing out along the path. It’s “classic” infinite scrolling but for recursive/hierarchical content.</p>
<p>Let’s agree for the moment that to stream or not to stream is <strong>not</strong> the question. The issue at hand is this: Although I’ve reduced the amount of data per user session the server and/or client handles at any time the size of the DOM updates being sent to the client still makes a material difference. I could argue that the worst case scenario, i.e. that for every tiny change the entire active dataset being re-rendered and sent to the client is acceptably small and “shouldn’t be a problem” which seems a fair assumption until you a) consider the impact of undetected runaway data caused by malicious users or by accident and b) consider the combined impact on the servers from a load and traffic perspective. Then it becomes imperative (as in essential and urgent) to implement the rendition of the data and its changes in whatever manner will ensure that as far as can be arranges only the parts that actually changed ever gets sent to the client.  LiveView already bares an impressive portion of this responsibility with it’s diffing and tracking of what parts of what assigns determined which portion of the HTML so that using the assigned DOM ids the affected parts of the DOM may be patched.</p>
<p>Now we know that the (as you put it) canonical use-case of LiveView involves linear sets of independent records as obtained from (either pure or streamed) lists provided to the component/rendering function as assigns. To flatten a recursively preloaded Ecto schema into a linear set of records and back again into the nested structures upon use, is rapidly turning from a solvable to a solved problem for me. That takes care of the linear list part.   At first glance it seems that by happenstance or foresight the way LiveView identify the parts of the DOM requiring updates create the opportunity to succinctly identify and propagate the patches to each node in the tree (given to LiveView as a list). Ostensibly, (and this could be crux of the matter) it matters not where in the DOM a node’s HTML is placed, it will patch that node’s HTML based on the ID with the re-generated HTML.</p>
<p>If the above were true in every sense it would have simplified matters a great deal. Still might, but it all rests on how LiveView updates the DOM.</p>
<p>Any assumption of <em>independence</em> between elements being given IDs as they are rendered in preparation for DOM updates could mean that when that portion of the DOM is updated it is replaced with the new HTML that just arrived. If that element/node however included other content for which there or don’t need to be newly generated HTML, will the new DOM tree be patched with the other content from the original DOM or will it be discarded?   Between what I’ve seen happening myself, what is described in the documentation and presentations and your feedback about streams and their issues, I’m getting lots of mixed signals. I’ve been warned of and have witnessed containers’s existing content hanging about indefinitely after an update. One particular example of this I can remember involved table rows I incorrectly added as headings without the appropriate identifier (data-phx-id) being impossible to get rid of. On the other hand you’re mentioning that (neither standard assigns or stream assigns in) LiveView handles container updates correctly. That’s more than a little confusing. Perhaps (though unlikely) it all true and the diffing and DOM update code in Phoenix works or doesn’t work at random but more likely it is merely opinionated about when it can and should do what. Whether it pertains to bugs, missing functionality or documented caveats, it should be possible to grasp what LiveView would and would not be able to handle under what conditions and then see to it that those conditions are met when writing the code.</p>
<p>Best course of action I can think of right now is to consult with the expert, which will do its own reply so he doesn’t have to suffer through all of the above.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="354617" data-batch-url="/posts/batch_likers">
                        0
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/using-streams-with-recursive-and-or-deeply-nested-schemas/69023/12">Post #11</a>
	                </div>
	            </div>
              <div id="likers-container-354617" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="354617"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #11"></div>
  </section>
</div>
    <div class="postbit" id="354619" data-post-id="354619">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="MarthinL" src="/assets/icons/user-9f439610.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  MarthinL
                      <span class="op-star" title="Thread Starter">
                        <img alt="OP" class="op-star-icon" src="/assets/thread-icons/thread-icon-thread-starter-df91e872.png" />
                      </span>
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p><a class="mention" href="/u/chrismccord" rel="nofollow">@chrismccord</a>, may we please have your expert (as author) opinion on the following:</p>
<p>When LiveView renders items from a list it may fairly assume that the DOM tree for each item is independent (or separate, non-overlapping). So when a change to such an item if picked up it could replace the old DOM node (with that data-phx-id) with the newly generated HTML for it (the same data-phx-id). That’s all fair and what we experience all day.</p>
<p>If that component, HTML and subsequent DOM tree in fact acts as  (probably indirect as in happening some &lt;ul&gt;, &lt;li&gt; &lt;detail&gt;, &lt;table&gt;, or plain &lt;div&gt;s down) a container for what had been rendered from other list elements,</p>
<ul>
<li>would that content get discarded (unless they once again present in the replacement HTML) or</li>
<li>could we arrange for such content to be transferred to the replacement DOM tree?</li>
</ul>
<p>If the above is meant to be happening by default, what conditions would break it and/or determine what gets replaced, discarded or transferred?</p>
<p>If it’s not default behaviour, is it supported through some options?</p>
<p>If it’s not supported at all, would it likely be bugs causing the instances I’ve witnessed where a container ends up with “dangling” content from previous updates?</p>
<p>Also, if it’s evident that I’m approaching this from an angle that’s orthogonal to your intentions with LiveView you’re more than welcom to point that out. I (for one, and at least one other) am building something with LiveView that may well be outside its design parameters.</p>
<p>The objective I’m chasing, so you can TL/DR the rest of the conversation, is implement infinite scrolling - style navigation through a vast recursive dataset and minimise the size of updates sent to clients as much as possible be excluding bogus changes.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="354619" data-batch-url="/posts/batch_likers">
                        1
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/using-streams-with-recursive-and-or-deeply-nested-schemas/69023/13">Post #12</a>
	                </div>
	            </div>
              <div id="likers-container-354619" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="354619"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #12"></div>
  </section>
</div>
    <div class="postbit" id="354676" data-post-id="354676">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="steffend" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/steffend/120/20548_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  steffend
                  </h3>
		          </div>
						
			          <div class="user-title">
									<span>Phoenix Core Team</span>
			          </div>
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>I don’t know if Chris will respond to this as well, but let me try to clarify some things.</p>
<p>First of all, LiveView provides a way to render HTML templates on the server and keep a client in sync with those. It does this by splitting the template into static and dynamic parts to only send those parts for subsequent updates that are affected by a change. LiveView’s change tracking works best for small assigns, where you can easily say that they either changed or not. As soon as lists or nested data structures are involved, things get tricky. LiveView has some optimizations for change-tracking maps, but it does not change-track lists. This means that if you do:</p>
<pre data-code-wrap="heex"><code class="lang-heex">&lt;ul&gt;
  &lt;li :for={item &lt;- @items}&gt;
    {item.name}
  &lt;/li&gt;
&lt;/ul&gt;
</code></pre>
<p>using regular assigns, LiveView will re-send all the list items when the list is updated. Even if you actually only append or prepend a single item to the list. There are two ways to address this:</p>
<ol>
<li>use streams</li>
<li>use a LiveComponent for each item</li>
</ol>
<h2><a name="p-354676-h-1-streams-1" class="anchor" href="#p-354676-h-1-streams-1" aria-label="Heading link" rel="nofollow"></a>1. Streams</h2>
<p>Streams are meant mainly as an optimization for collections of items - say your typical table that contains many rows. They provide a way to keep those items rendered on the client without the server needing to keep all of them in memory. Because the server does not know which items are actually rendered on the client, changes sometimes do require more coordination, for example to specify at which position an item should be inserted or updated.</p>
<p>Streams work by designating a DOM node as a stream container (marked with <code>phx-update="stream"</code>). On the server, the only special handling that streams receive is that their content is pruned (-&gt; changed back into an empty list) after each render. Let’s look at an example:</p>
<pre data-code-wrap="heex"><code class="lang-heex">&lt;ul id="my-stream" phx-update="stream"&gt;
  &lt;li :for={{id, item} &lt;- @streams.items}&gt;
    {item.name}
  &lt;/li&gt;
&lt;/ul&gt;
</code></pre>
<p>When you initially do</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">stream(socket, :items, [%{id: 1, name: "First"}, %{id: 2, name: "Second"}, %{id: 3, name: "Third"}])
</code></pre>
<p>the client will receive the following HTML (we won’t look at the diff structure in detail, but on the client LiveView always reconstructs the full HTML after applying a diff and feeds that HTML to morphdom):</p>
<pre data-code-wrap="html"><code class="lang-html">&lt;ul id="my-stream" phx-update="stream"&gt;
  &lt;li id="items-1"&gt;First&lt;/li&gt;
  &lt;li id="items-2"&gt;Second&lt;/li&gt;
  &lt;li id="items-3"&gt;Third&lt;/li&gt;
&lt;/ul&gt;
</code></pre>
<p>The diff also contains metadata for each streamed item (which stream it belongs to, its id, its position in the stream and the stream limit). When you now do something on the server that does not touch the stream, subsequent patches to the DOM basically render an empty container:</p>
<pre data-code-wrap="html"><code class="lang-html">&lt;ul id="my-stream" phx-update="stream"&gt;&lt;/ul&gt;
</code></pre>
<p>and the client handles those containers in a special way to preserve all stream items until they are deleted. When you now <code>stream_insert</code> something, the client will receive new HTML including the new item, and together with the metadata, it will know how to properly insert this item as a child in the stream container.</p>
<p>The important part is that stream children are always rendered as direct child of a stream container. So trying to transform a nested structure into a single stream will not work if you cannot also render your structure as a flat list of DOM nodes.</p>
<h2><a name="p-354676-h-2-livecomponents-2" class="anchor" href="#p-354676-h-2-livecomponents-2" aria-label="Heading link" rel="nofollow"></a>2. LiveComponents</h2>
<p>LiveComponents are meant to provide a way to encapsulate components that handle their own events. LiveComponents are not meant to optimize server memory usage. The server will always hold all LiveComponent assigns in memory (ignoring temporary_assigns and streams inside LiveComponents, that can be used for optimizations). They are special in how they affect DOM patching, so that’s why they also play an important role when talking about rendering a list of items. The important part is that each LiveComponent performs its own change tracking. So when having something like this:</p>
<pre data-code-wrap="heex"><code class="lang-heex">&lt;ul&gt;
  &lt;.live_component :for={item &lt;- @items} id={item.id} module={...} item={item} /&gt;
&lt;/ul&gt;
</code></pre>
<p>and you change the <code>@items</code> assign, each LiveComponent will be updated, it calculates its changes and send the necessary diffs to the client. If nothing changed, only the updated order of LiveComponents is sent to the client, as a list of integer IDs. Importantly, LiveComponents in diffs are basically treated as a flat map of <code>%{id =&gt; diff}</code>.</p>
<p>LiveComponents are updated in the following cases:</p>
<ol>
<li>a render is triggered that renders its <code>&lt;.live_component /&gt;</code> tag</li>
<li><code>send_update</code> is used to directly update a component with new assigns</li>
</ol>
<h2><a name="p-354676-questions-3" class="anchor" href="#p-354676-questions-3" aria-label="Heading link" rel="nofollow"></a>Questions</h2>
<p>Now, before looking at a possible solution for arbitrarily nested data structures, I want to try to directly answer your questions:</p>
<aside class="quote no-group" data-username="MarthinL" data-post="13" data-topic="69023">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/letter_avatar_proxy/v4/letter/m/3da27b/48.png" class="avatar"> MarthinL:</div>
<blockquote>
<p>When LiveView renders items from a list it may fairly assume that the DOM tree for each item is independent (or separate, non-overlapping). So when a change to such an item if picked up it could replace the old DOM node (with that data-phx-id) with the newly generated HTML for it (the same data-phx-id). That’s all fair and what we experience all day.</p>
</blockquote>
</aside>
<p>LiveView uses morphdom to make sure that the rendered DOM looks like the HTML it generates from the diffs it receives from the server. So LiveView does not “replace” the old DOM node with the new one, but (if possible) updates it in place. This patching applies to all children of a patched element as well. Special handling is in place for nodes with <code>phx-update</code> (streams, ignore, append/prepend).</p>
<aside class="quote no-group" data-username="MarthinL" data-post="13" data-topic="69023">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/letter_avatar_proxy/v4/letter/m/3da27b/48.png" class="avatar"> MarthinL:</div>
<blockquote>
<p>If that component, HTML and subsequent DOM tree in fact acts as (probably indirect as in happening some <code>&lt;ul&gt;, &lt;li&gt; &lt;detail&gt;, &lt;table&gt;</code>, or plain <code>&lt;div&gt;</code>s down) a container for what had been rendered from other list elements,</p>
<ul>
<li>would that content get discarded (unless they once again present in the replacement HTML) or</li>
<li>could we arrange for such content to be transferred to the replacement DOM tree?</li>
</ul>
</blockquote>
</aside>
<p>If the reconstructed HTML from the diff does not contain the nested old content, it is discarded, unless it is rendered inside an element with <code>phx-update="ignore"</code> or inside a nested <code>phx-update="stream"</code>.</p>
<aside class="quote no-group" data-username="MarthinL" data-post="13" data-topic="69023">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/letter_avatar_proxy/v4/letter/m/3da27b/48.png" class="avatar"> MarthinL:</div>
<blockquote>
<p>If it’s not default behaviour, is it supported through some options?</p>
</blockquote>
</aside>
<p>I will go into this in detail in the next section.</p>
<aside class="quote no-group" data-username="MarthinL" data-post="13" data-topic="69023">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/letter_avatar_proxy/v4/letter/m/3da27b/48.png" class="avatar"> MarthinL:</div>
<blockquote>
<p>If it’s not supported at all, would it likely be bugs causing the instances I’ve witnessed where a container ends up with “dangling” content from previous updates?</p>
</blockquote>
</aside>
<p>You’d need to provide more details on when you’ve seen “dangling” content. I can think of one instance where this is expected: when you render a non-stream item in a <code>phx-update="stream"</code> container. In this case, such items are rendered and updated in the DOM, but they are never removed unless the whole container is removed. (This behavior is documented.)</p>
<h2><a name="p-354676-a-way-to-provide-surgical-updates-for-nested-data-structures-4" class="anchor" href="#p-354676-a-way-to-provide-surgical-updates-for-nested-data-structures-4" aria-label="Heading link" rel="nofollow"></a>A way to provide surgical updates for nested data structures</h2>
<p>By leveraging both Streams and LiveComponents, you should be able to achieve what you want: patching only parts of the DOM that are affected by a change. The idea is that you transform your data structure in such a way that lists are rendered as streams of live components, that can themselves contain more streams of live components. If you provide those live components a deterministic ID, that you can also reference when you need to perform a nested update, you can surgically patch the DOM with minimal diff over the wire by using <code>send_update</code>, targeting the individual nested component. Let’s look at an example:</p>
<p>We’re trying to render a treeview representing files and folders. The files are stored in a SQL database where each entry has a type <code>file</code> or <code>folder</code> and each entry has a <code>parent_id</code> that points to the parent folder. When we get this data from the database, we get a nested structure:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">%FileEntry{
  id: 1,
  type: :folder,
  name: "root",
  # preloaded with the parent_id
  children: [
    %FileEntry{
      id: 2,
      type: :file,
      name: "foo.txt",
      children: nil
    },
    %FileEntry{
      id: 3,
      type: :folder,
      name: "subfolder",
      children: [
        %FileEntry{
          id: 4,
          type: :file,
          name: "bar.txt",
          children: nil
        }
      ]
    }
  ]
}
</code></pre>
<p>This could be nested arbitrarily deep. We’re not required to fully render the tree. Our <code>list_files</code> function could only return a few levels, with further children being not preloaded. Now, let’s look at the code to render this:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">defmodule MyAppWeb.TreeView do
  def mount(_params, _session, socket) do
    # this would be the result of a query to the database, so probably more like
    # tree = Files.list_files(parent: nil, depth: 2)
    tree = [
      %FileEntry{
        id: 1,
        type: :folder,
        name: "root",
        # preloaded with the parent_id
        children: [
          %FileEntry{
            id: 2,
            type: :file,
            name: "foo.txt",
            children: nil
          },
          %FileEntry{
            id: 3,
            type: :folder,
            name: "subfolder",
            children: [
              %FileEntry{
                id: 4,
                type: :file,
                name: "bar.txt",
                children: nil
              },
              %FileEntry{
                id: 5,
                type: :folder,
                name: "subfolder 2",
                children: :not_loaded
              }
            ]
          }
        ]
      }
    ]

    {:ok, stream(socket, :tree, tree)}
  end

  def render(assigns) do
    ~H"""
    &lt;ul id="tree" phx-update="stream"&gt;
      &lt;.live_component
        :for={{dom_id, entry} &lt;- @streams.tree}
        module={Example.TreeComponent}
        id={entry.id}
        dom_id={dom_id}
        entry={entry}
      /&gt;
    &lt;/ul&gt;

    &lt;button phx-click="rename_bar"&gt;Rename bar.txt&lt;/button&gt;
    """
  end

  # assuming updates to individual entries are received via PubSub
  def handle_info({:update, entry}, socket) do
    send_update(Example.TreeComponent, id: entry.id, entry: entry)

    {:noreply, socket}
  end

  def handle_event("rename_bar", _params, socket) do
    # simulate a file event that could also be sent via PubSub
    send(self(), {:update, %FileEntry{id: 4, type: :file, name: "bar - #{DateTime.utc_now()}.txt", children: nil}})
    {:noreply, socket}
  end
end
</code></pre>
<p>and then the TreeComponent:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">defmodule Example.TreeComponent do
  use Phoenix.LiveComponent

  # subsequent updates don't affect the children by default;
  # those are updated on their own
  def update(assigns, %{assigns: %{entry: _}} = socket) do
    {:ok, assign(socket, assigns)}
  end

  def update(assigns, socket) do
    entry = assigns.entry

    # this is a memory optimization to not keep all children in memory
    socket = case entry do
      %{type: :folder, children: [_ | _ ] = children} -&gt;
        socket
        |&gt; stream(:children, children)
        |&gt; assign(entry: Map.put(entry, :children, []))

      _ -&gt;
        socket
        |&gt; stream(:children, [])
        |&gt; assign(entry: entry)
    end

    {:ok, socket}
  end

  def render(assigns) do
    ~H"""
    &lt;li&gt;
      {@entry.name}
      &lt;ul :if={@entry.type == :folder} id={"folder-#{@entry.id}"} phx-update="stream"&gt;
        &lt;.live_component
          :for={{dom_id, entry} &lt;- @streams.children}
          module={Example.TreeComponent}
          id={entry.id}
          dom_id={dom_id}
          entry={entry}
        /&gt;
      &lt;/ul&gt;
      &lt;button :if={@entry.children == :not_loaded} phx-click="load_children" phx-target={@myself}&gt;Load children&lt;/button&gt;
    &lt;/li&gt;
    """
  end

  def handle_event("load_children", _params, socket) do
    # this would probably look more like
    # children = File.list_files(parent: socket.assigns.entry.id, depth: 2)
    children = [
      %FileEntry{
        id: 100 + floor(:rand.uniform(1000)),
        type: :file,
        name: "more.txt",
        children: nil
      }
    ]

    socket
    |&gt; assign(:entry, Map.put(socket.assigns.entry, :children, []))
    |&gt; stream(:children, children)
    |&gt; then(&amp;{:noreply, &amp;1})
  end
end
</code></pre>
<p>Here’s a single file sample with the full code: <a href="https://gist.github.com/SteffenDE/61d5915d506f8eb5e62d73c41d4536be" class="inline-onebox" rel="noopener nofollow ugc">LiveView File Tree optimized with Streams + LiveComponents · GitHub</a></p>
<p>I hope this clarifies things a bit. Let me know if you have further questions!</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="354676" data-batch-url="/posts/batch_likers">
                        18
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/using-streams-with-recursive-and-or-deeply-nested-schemas/69023/14">Post #13</a>
	                </div>
	            </div>
              <div id="likers-container-354676" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="354676"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-most-liked cat-most-liked" title="One of the top 3 liked posts in this thread!"></div>
  </section>
</div>
    <div class="postbit" id="354681" data-post-id="354681">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="garrison" src="/assets/icons/user-9f439610.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  garrison
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>This is a stunning and thorough reply. Thank you for saving me a lot of time! <img src="https://forum.elixirforum.com/images/emoji/apple/slight_smile.png?v=15" title=":slight_smile:" class="emoji" alt=":slight_smile:" loading="lazy" width="20" height="20"></p>
<p>There are a couple of things I would like to add:</p>
<p>First, since your (last) example uses streams, you still need to know which component should receive a given update. In the spirit of my previous comments on this topic I would like to point out that this is not always easy. For example, say you wanted to compute the number of files in each folder (recursively) at runtime (there are legitimate reasons for this). It would quickly become difficult to determine which components should receive an update (the correct answer <em>in this case</em> would be to update all of the parents of an updated file, recursively). There are many more features you might build which require updates across arbitrary nodes, where the computation of that set of nodes is nontrivial.</p>
<p>In this case you could simply use a pure LiveComponent approach and retain the wire benefits (at the cost of more computation for diffs, which is usually a worthy tradeoff IMO).</p>
<p>Second, I want to talk about a pattern I ran into implementing this feature. When updated records come in (over pubsub or from the database), it is necessary to “stitch” them back into the tree structure. As Elixir is functional, this operation is, frankly, annoying. You can avoid this by storing each set of records in a map <code>%{primary_key =&gt; struct}</code> and then essentially “joining” them into the tree structure in-memory (this is a form of indirection).</p>
<p>For example, you might have a <code>nodes</code> table which references both <code>files</code> and <code>folders</code> through foreign keys. You might load the <code>nodes</code> into a tree, and then load the <em>full</em> list of <code>files</code> and <code>folders</code> into maps. Then, when you receive an updated <code>%File{}</code>, you need only update the map <code>%{file.id =&gt; file}</code>.</p>
<p>Then you can render out a tree <em>yourself</em> containing only the relevant data. For example you might produce a tree like this:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">%{
  type: :folder,
  file_count: 100,
  children: [
    %{type: :file, ...},
    ...
  ],
}
</code></pre>
<p>And then you can pass <em>that</em> tree to the LiveComponents (which themselves form a tree), and they will nicely diff those attributes for you, all the way down the tree. Note that if you give the components static ids (the primary keys of the nodes, here), you can even <em>move entire subtrees</em> around with tiny updates (a wonderful optimization that comes from LiveView).</p>
<p>Note that you can now recursively sum the file counts up the tree when you compute this “rendered tree”, as well as materialize any changes to the files and folders <em>without</em> having to reload the <em>entire</em> tree (with joins) from the database for every measly operation (like a file rename).</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="354681" data-batch-url="/posts/batch_likers">
                        0
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/using-streams-with-recursive-and-or-deeply-nested-schemas/69023/15">Post #14</a>
	                </div>
	            </div>
              <div id="likers-container-354681" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="354681"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #14"></div>
  </section>
</div>
    <div class="postbit" id="354683" data-post-id="354683">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="garrison" src="/assets/icons/user-9f439610.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  garrison
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<aside class="quote no-group" data-username="MarthinL" data-post="12" data-topic="69023">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/letter_avatar_proxy/v4/letter/m/3da27b/48.png" class="avatar"> MarthinL:</div>
<blockquote>
<p>For context, I’m not looking at hundreds of thousands items but hundreds of millions per regional federation server cluster.</p>
</blockquote>
</aside>
<p>This is <em>very</em> important context, that is an enormous tree! Obviously at that size you cannot fit it in memory on the server, on the client, or probably even in the database (you likely have it on disk).</p>
<p>I think the (excellent) reply above clarifies the performance of LiveView (streams or not), but there’s something else important to highlight here:</p>
<p>With this amount of data, there is no way a user is going to be able to parse (in their head) even a fraction of a percent of the total tree. So what you have, more than anything else, is a UX problem; how do you choose which parts of the tree to present?</p>
<p>I can’t comment on this further without knowing your actual use case, and I’m sure you’ve thought about this already, but there are many possibilities here: pagination, search, and so on.</p>
<p>Only once you have reduced the dataset down to the thousands (and it is therefore comprehensible to mere mortals!) is any of the performance advice here going to apply.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="354683" data-batch-url="/posts/batch_likers">
                        0
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/using-streams-with-recursive-and-or-deeply-nested-schemas/69023/16">Post #15</a>
	                </div>
	            </div>
              <div id="likers-container-354683" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="354683"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #15"></div>
  </section>
</div>
    <div class="postbit" id="354686" data-post-id="354686">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="MarthinL" src="/assets/icons/user-9f439610.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  MarthinL
                      <span class="op-star" title="Thread Starter">
                        <img alt="OP" class="op-star-icon" src="/assets/thread-icons/thread-icon-thread-starter-df91e872.png" />
                      </span>
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<aside class="quote no-group" data-username="garrison" data-post="16" data-topic="69023">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/letter_avatar_proxy/v4/letter/g/3bc359/48.png" class="avatar"> garrison:</div>
<blockquote>
<p>With this amount of data, there is no way a user is going to be able to parse (in their head) even a fraction of a percent of the total tree. So what you have, more than anything else, is a UX problem; how do you choose which parts of the tree to present?</p>
<p>I can’t comment on this further without knowing your actual use case, and I’m sure you’ve thought about this already, but there are many possibilities here: pagination, search, and so on.</p>
<p>Only once you have reduced the dataset down to the thousands (and it is therefore comprehensible to mere mortals!) is any of the performance advice here going to apply.</p>
</blockquote>
</aside>
<p>You’d migh have missed that explicitly stated all of this as part of explainging the context. I even brought up much of it in response to your initial  criticism of Streams. So yes, all of that is valid observations I’ve long since considered as fundamental to my application design. What is under discussion right now is after all of that is thouroughly recognised and implemented, what remains is a small “page” or window of data actively being presented to the user which usually means a single node from somewhere in the tree being considered the current root of what is being rendered and one would still like to avoid sending the whole tree’s worth of data everything a sparrow coughs.</p>
<p>I’m leaning towards using a MapSet rather than a plain map as the basis by which to achieve what you described as wanting to do with a map but it seems we’re heading in the same general direction. But it won’t be of much help if i (we) cannot arrange for the unchanging children to remain in the container when only the container has changed.</p>
<p>If I understand <a class="mention" href="/u/steffend" rel="nofollow">@steffend</a>’s very eloborate answer (thank you so much) he’s essentially saying that if saving on the network traffic and consequential delays is important enough on can adress the problem by using more server memory to maintain each (presumably) node/level’s re-rendering context as the state maintained by a LiveComponent.  That’s what I heard. It’s a way that might would probablyy work (althrough I must study the example more in order to connect with exactly where the it is achieved that the parent is updated with the same children rather than new ones. I’m sure it’s there, I’m just ever so slightly overwhelmed by the given example right now.</p>
<p>Having said that, the prospect of acrificing server memory to order to save the oad of the network and client made me think of another approach with a different sacrifice. In my case, where the current page / window getting rendered as HTML is very well defined as a small set of root nodes, usually one, and a maximum recursion level, it might just be that the cheapest colution in terms of resouces and time is to simply reload the data from database each time and do the diff on that. It might still require a LiveComponent to arange for the right diffing and patching, but it would dramatically reduce the memory footprint of the LiveComponent’s state per user session per node.</p>
<p>That last (per node) is an daring assumption. I’m not sure *and havent been able to tell uite yet) if the entire current page would be rendered in one (stateful) LiveComponent or it would require a LiveComponent for each cluster of associated clusters of nodes representing one node in the recustive pattern.If either of you is particularly clear on that  please put me on the right path.</p>
<p>The other unknown to me so far as stated is exactly what/where in the example arrangement has the impact/side effect of allowing the container to be updated without changing the children. My rough guess and understanding is that it’s a consequence of the diffing being done by the LiveComponent finding that the new HTML for parts of it h=is the same, but I’m not sure if that’s right or what controls that. PLus it seems a tall order for that diffing to pick that up several layers deep in the recursive structure.</p>
<p>To clarify that last point abount levels. In my use case the recursion is indirect, meaning that it’s not simply an Ecto struct with a parent_id resulting in each tree having a has_many association that reads like a list of children. In my case a logical recursive node consists of quite a few (order of magnitude 6-10) different records in various associations with the each other, multiple smaller list of children considered acceptibly small to update as a whole every time. But at sime point that constellation of Ecto schemas starts to repeat itself with a single Ecto schema struct on the parent side and a list of the same Ecto schema structs on the other. I’ve made some progress chopping my tree (after pagination) up into these recursive nodes by walking depth first through the tree and at the hand of a keyword list akind to how preloads are specified in order to know when to draw the lines between the content that goes together and the content that should go into the next node. It’s easy to tell sibling nodes from each other because they are typically in a list of the kind at the head of the preload list, and to traverse a preloaded trea is childs play using reflection. The only thing to look out for is when the preloaded structure reaches the recustion point as indicated by where the preload spec returns to one or a list of the same spec as it’s head.  That’s what I refer to as a node, and the assumption I’ve trying to check is whether or not using the LiveComponent approach would involce instansiating a LiveComponent instance for each node or once only per user to keep the entire acative section of the tree in state memory.</p>
<p>Anyway, thank you for your time and considered responses. Like observed I’ve a non-trivial use case and I concern myself primarily with ways and means to keep the user experience from overwhelming the user. Part of that involves innovative ways of showing the and allowing the user to control deep he looks and even assign his own summations for things once he’s understood its meaning, but another part of it is to ensure as far as I can that the interface itself appears smooth as a baby’s bottom which means not getting interrupted by spinners and dead screen time. I’m willing to dedicate the server resources required to do achieve that, but must be realistic about it as well. Generally speaking my database servers are rigged for read performance so it isn’t entrely beyond the realm of possibility that reloading could be a better use of server resources than keeping things in memory. Already I’ve done some Ecto testing to compare an actual tail recursive loading algorithm with a single preload using a arbitrarily nested preload statement. For the same dataset the recursively generated list of preloads performed the preload using about half the queries usd by preload using actual recursion.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="354686" data-batch-url="/posts/batch_likers">
                        0
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/using-streams-with-recursive-and-or-deeply-nested-schemas/69023/17">Post #16</a>
	                </div>
	            </div>
              <div id="likers-container-354686" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="354686"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #16"></div>
  </section>
</div>
    <div class="postbit" id="354690" data-post-id="354690">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="MarthinL" src="/assets/icons/user-9f439610.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  MarthinL
                      <span class="op-star" title="Thread Starter">
                        <img alt="OP" class="op-star-icon" src="/assets/thread-icons/thread-icon-thread-starter-df91e872.png" />
                      </span>
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<aside class="quote no-group" data-username="garrison" data-post="16" data-topic="69023">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/letter_avatar_proxy/v4/letter/g/3bc359/48.png" class="avatar"> garrison:</div>
<blockquote>
<p>I can’t comment on this further without knowing your actual use case, and I’m sure you’ve thought about this already, but there are many possibilities here: pagination, search, and so on.</p>
</blockquote>
</aside>
<p>I wouldn’t expect you to. The application conveys its own use case best, and I’ve learnt not to attempt it in any other way. Suffice it to say that every user starts engaging with the big world out there by focussing only on their own data. Gradually they branch out to engage with other (shared) data fom which they progressively bring how they see it in to their own dataset. The focus is on condensing large volumes of information, once sufficiently explored to form a single word or sentence about it for yourself at that time, to replace that entire branch of the big tree with your take on it, until some later time when something challenges or clashes with your summary view draws you back into the source material and multiple levels of summaries compiled by yourself and others, to look for where you’ve misunderstood . In the end, each other sits with logically the whole tree condensed into words and concepts by which they understand it. From the top level, very level you engage with is ultimately intended to fit on a single page. More than that an you’re encouraged to further reduce it. As humans we can deal with, depending on a few geneti and environmental factors, with 7 plus or minus 2 concurrent thoughts in our minds. That’s not a lot. IDEF/0 took that a little further and made it formally part of the methodology and toolset saying that every activity must be broken down into between 3 and 6 sub-activities. Less than 3 or more than six means you’ve not comprehended the activity at that level of recursion and you need to do better. My rules aren’t that strict but i’ve pretty much adopted a similar notion - there’s no point in this deep hierarchy essentially repeating itself without getting anywhere new, and any list of more than a few items is testimory that you’ve not yet spent enough time abstracting the list into sublists that impact each other and belong together. That sort of thing. Again, I cannot convey a fraction of one percent of the whole concept here or anywhere I’m foced to use language designed for telling stories.</p>
<p>There is (very obviously) a great deal more to it, but that is in a nutshell the general concept by which the user experience compensates for the overwhelming complexity of such a vast tree. For more detail you’d have to wait until you receive your invitation to join the program from someone you know personally know. It may take a while, but it should get to you eventually.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="354690" data-batch-url="/posts/batch_likers">
                        0
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/using-streams-with-recursive-and-or-deeply-nested-schemas/69023/18">Post #17</a>
	                </div>
	            </div>
              <div id="likers-container-354690" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="354690"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #17"></div>
  </section>
</div>
    <div class="postbit" id="354692" data-post-id="354692">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="garrison" src="/assets/icons/user-9f439610.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  garrison
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<aside class="quote no-group" data-username="MarthinL" data-post="17" data-topic="69023">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/letter_avatar_proxy/v4/letter/m/3da27b/48.png" class="avatar"> MarthinL:</div>
<blockquote>
<p>I’m leaning towards using a MapSet rather than a plain map</p>
</blockquote>
</aside>
<p>I am unsure of where exactly you are employing these MapSets but the reason I mention using a <code>map</code> is because retrieval by primary key is <code>O(1)</code> and I am essentially performing a hash join in Elixir code. Using a set (or list) would perform much worse and gains nothing in terms of functionality.</p>
<aside class="quote no-group" data-username="MarthinL" data-post="17" data-topic="69023">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/letter_avatar_proxy/v4/letter/m/3da27b/48.png" class="avatar"> MarthinL:</div>
<blockquote>
<p>he’s essentially saying that if saving on the network traffic and consequential delays is important enough on can adress the problem by using more server memory to maintain each (presumably) node/level’s re-rendering context</p>
</blockquote>
</aside>
<p>That is correct, this is declarative in the “declarative vs imperative UI” paradigm we have discussed at length, and the memory is the price.</p>
<aside class="quote no-group" data-username="MarthinL" data-post="17" data-topic="69023">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/letter_avatar_proxy/v4/letter/m/3da27b/48.png" class="avatar"> MarthinL:</div>
<blockquote>
<p>it might just be that the cheapest colution in terms of resouces and time is to simply reload the data from database each time and do the diff on that</p>
</blockquote>
</aside>
<p>This is an implementation detail of your backend but it does not affect the “LiveView to Client” (wire) diffs we are talking about. In order to diff the message to the client, you have to store the old and new states. LiveComponents will do that for you, Streams will not (by design).</p>
<aside class="quote no-group" data-username="MarthinL" data-post="17" data-topic="69023">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/letter_avatar_proxy/v4/letter/m/3da27b/48.png" class="avatar"> MarthinL:</div>
<blockquote>
<p>one (stateful) LiveComponent or it would require a LiveComponent for each cluster of associated clusters of nodes</p>
</blockquote>
</aside>
<p>Where you put the LiveComponents would determine the granularity of the diffs. For the smallest diffs you want to use them as the elements of lists (<code>:for</code> comprehensions) because LV does not properly diff lists on its own. In practice you would probably use one LiveComponent per node in your graph.</p>
<aside class="quote no-group" data-username="MarthinL" data-post="17" data-topic="69023">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/letter_avatar_proxy/v4/letter/m/3da27b/48.png" class="avatar"> MarthinL:</div>
<blockquote>
<p>The other unknown to me so far as stated is exactly what/where in the example arrangement has the impact/side effect of allowing the container to be updated without changing the children.</p>
</blockquote>
</aside>
<p>Each LiveComponent must be given a unique ID (unique within the page), and this id allows the LiveView client to send diffs directly to the part of the DOM where they are needed (even if arbitrarily nested).</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="354692" data-batch-url="/posts/batch_likers">
                        0
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/using-streams-with-recursive-and-or-deeply-nested-schemas/69023/19">Post #18</a>
	                </div>
	            </div>
              <div id="likers-container-354692" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="354692"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #18"></div>
  </section>
</div>
    <div class="postbit" id="354747" data-post-id="354747">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="MarthinL" src="/assets/icons/user-9f439610.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  MarthinL
                      <span class="op-star" title="Thread Starter">
                        <img alt="OP" class="op-star-icon" src="/assets/thread-icons/thread-icon-thread-starter-df91e872.png" />
                      </span>
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<aside class="quote no-group" data-username="MarthinL" data-post="17" data-topic="69023">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/letter_avatar_proxy/v4/letter/m/3da27b/48.png" class="avatar"> MarthinL:</div>
<blockquote>
<p>I must study the example more in order to connect with exactly where the it is achieved that the parent is updated with the same children rather than new ones. I’m sure it’s there, I’m just ever so slightly overwhelmed by the given example right now.</p>
</blockquote>
</aside>
<p>Thanks <a class="mention" href="/u/steffend" rel="nofollow">@steffend</a>, I wasn’t sure but I finally spotted the place in your code (a.k.a caught your gist <img src="https://forum.elixirforum.com/images/emoji/apple/slight_smile.png?v=15" title=":slight_smile:" class="emoji" alt=":slight_smile:" loading="lazy" width="20" height="20"> ) where it is explicit that there’d be a LiveComponent per tree node. I’m now weighing up the cost (in overheads) keeping each node state in memory separately vs keeping the displayed windows of the large tree in memory as a whole vs loading it from database as needed.</p>
<p>I need to confirm my understanding. You have the comment:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir"> # subsequent updates don't affect the children by default;
  # those are updated on their own
</code></pre>
<p>and I’m not sure I understand exactly what establishes and/or affect that default behaviour. Is it that LV diffing stops walking the tree with it encounters another (or the same) LiveComponent, or is it because of something your code explicitly does or doesn’t do?</p>
<p>I can see how and that it would work as advertised, and once I’m sure how to control the behaviour, it definitely could also work for me. It’s not the end of my journey though, for the simple reason that once I’m in control of the size of the loaded portion of the gigantic tree I might not need to use streams for pagination and further, leaving only the update vocabulary it provides as reason to use streams. I’ll need to give this more thought.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="354747" data-batch-url="/posts/batch_likers">
                        0
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/using-streams-with-recursive-and-or-deeply-nested-schemas/69023/20">Post #19</a>
	                </div>
	            </div>
              <div id="likers-container-354747" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="354747"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #19"></div>
  </section>
</div>
    <div class="postbit" id="354755" data-post-id="354755">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="MarthinL" src="/assets/icons/user-9f439610.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  MarthinL
                      <span class="op-star" title="Thread Starter">
                        <img alt="OP" class="op-star-icon" src="/assets/thread-icons/thread-icon-thread-starter-df91e872.png" />
                      </span>
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p><strong>Update 1:</strong></p>
<p><a class="mention" href="/u/steffend" rel="nofollow">@steffend</a> posted a <a href="https://forum.elixirforum.com/t/using-streams-with-recursive-and-or-deeply-nested-schemas/69023/14" rel="nofollow">complete example</a> of how one could achieve the effect I described leveraging LiveComponent and Streams to keep each node’s data in server memory. Thank you kindly.</p>
<p><strong>Update 2:</strong><br>
I had a few key insights since starting this discussion (and duly apologise for not thinking of it earlier).</p>
<ol>
<li>
<p>Lists are not arrays, they merely exhibit some array-like semantics.<br>
Being aware of ways to keep tree data in arrays I’ve been trying to come up with a way to achieve a kind of dual nature version of recursive data whereby it’s a both a (streamable) list and (recursively loaded) struct at the same time i.e. without having to maintain two copies of it. Then it dawned on me that actually lists are not arrays but exactly the recursive structure I’m looking for because the way they are implemented in Elixir to allow for nested lists they already maintain a reference to a parent which is nil for non-nested list heads.</p>
</li>
<li>
<p>A key part of the problem is the gigantic and indeterminate size of the recursive underlying data resulting in bloated memory and/or DOM deltas. I know I mentioned it, to myself and in this thread, but didn’t take note of one implied observation until now. My (and likely most) recursive data has two parts - the recursive structure and the data it structures. Compared to the combined node contents, the tree/graph structure is quite small. Seeing that I already employ techniques to limit how much of the large tree is loaded and presented to a user at a time, the structural part of that is amost certainly small enough to keep in memory as a whole to serve the purpose of what Steffen calls “surgical updates”.</p>
</li>
<li>
<p>After combining insights 1) and 2) above, it dawned upon me that if I extract only the (integer, in my case) id’s of the recursive element from the database or loaded structure, it can accurately and succinctly represent the tree strucure in a list, so that</p>
</li>
</ol>
<blockquote>
<p>[1,2,[3,4,[5,6,7],8],9]</p>
</blockquote>
<p>represents the tree</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">-1
-2
+3
| -4
| +5
| | -6
| | -7
| -8
-9  
</code></pre>
<p>[Edit] The non-obivious mapping in the above is that a nested list maintains a “parent” element reference. Semantically a list is [head|tail] where the tail is also a list, so to store a tree in there simply means adding the convention that<br>
By how Elixir’s (linked) lists map onto erlang’s [head|tail] the internal structure in both the above cases are the same. It’s actually list operations themselves and the Enum behaviour that creates the illusion that the recursive structure is linear.</p>
<p>I guess it’s in honour of simplicity that the goto approach for traversing a nested list is to flatten it first, but it’s trivial to write a function which mimics each, map, reduce, fold or fold for raw nested lists. Not that it’s important, just an observation.</p>
<p>The implication of this insight is that I do not need to retain the whole cluster of Ecto structures to remember where everything fits into the tree. A nested list divorced from any Ecto struct metadata can store the recursive structure without any loss of information. It’s trivial to use identifiers from the list representation of the tree to reapply metadata to reload or reconstruct nodes into recursive Ecto struct when that’s needed.</p>
<ol start="4">
<li>I probably would, to be explicit and prevent the structural information being discarded by an innocent flatten operation along the line, extract the structure into a list where single integeres represent leaf nodes and nodes with children are represented by a tuple containing the id of the parent element and a list of its children, i.e. the same tree as above would be:</li>
</ol>
<pre data-code-wrap="elixir"><code class="lang-elixir"> [
  1,
  2,
  {3, [
       4,
       {5, [
            6,
            7]
       },
       8
     ]
  },
  9
]
</code></pre>
<p>That would leave me with a small enough structure for a TreeView LiveComponent to retain as state for the whole (currently visible) part of the impossibly large tree, directly manipulate cheaply and consult to determine what changes to propagate and what to do with changes propagated from elsewhere. The meat of the node, i.e. cluster of associated schemas can then be sourced and supplied at will as a traditional flat List of such structs or a Map for quick access or a MapSet for quick access and automatic de-duplication, sourced from the database at the most opportune time, i.e either ahead of time with a bg recursive preload which is then flattened for the initial mount, as individual fetches by id if that’s required or by reloading and further preloading a node for which the children were not loaded because of they were too deeply nested for the window at that time.</p>
<p>In summary, I’m currently of opinion that I (and possibly others with the same use case) can prevent the recursive nature of the data from impeding on the regular and sober use of LiveView (with or without Streams) by isolating and removing the structural aspect of the data and explicitly dealing with that data as a separate concern. I’d still need to give both concerns due attention to avoid unwanted behaviour, but they would no longer cripple each other.</p>
<p>If it turns out (I’ve started asking what I hope would be the right questions) that the only or most effective way to get LiveView not replace existing list of children rendered into a parent when the parent is patched is to make the children LiveComponents themselves, then that’s easy and cheap to arrange because the LiveComponent would have minimal state. It would be ideal though if the nodes can be rendered as function components (i.e. no state) and still allow control over when the children should be retained or dumped when the parent node is patched. I’ll continue to investigate if that is feasible.</p>
<p>The same approach would also work for other key types including composite keys, tenanted or partiioned data, the structure would just grow bigger with the additional data per node to uniquely identify it.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="354755" data-batch-url="/posts/batch_likers">
                        0
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/using-streams-with-recursive-and-or-deeply-nested-schemas/69023/21">Post #20</a>
	                </div>
	            </div>
              <div id="likers-container-354755" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="354755"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #20"></div>
  </section>
</div>
</template></turbo-stream><turbo-stream action="replace" target="load-more-container"><template><div id="load-more-container" class="load-more-container">
    <a class="load-more-button" data-turbo-stream="true" href="/topics/69023/load_more?page=3">Load more posts</a>
</div></template></turbo-stream>