… or put another way, is there a way to access the dynamic state of a component instance that is held by LV internally for diffing purposes?
Thanks
… or put another way, is there a way to access the dynamic state of a component instance that is held by LV internally for diffing purposes?
Thanks
Are you sure LiveView does not already perform this optimization?
It certainly does, but my scenario is relying on the appended container + temporary assigns pattern to render only the contained component instances that changed (not necessarily just appending the new ones). Therefore it is my code that needs to effectively diff (filter out) the component instances that haven’t changed.
The issue here is that my component state assigns are dynamically computed from several data sources, so, unless I find a way to compare the assigns with the state for the same component instances held by LV, I would either have to assign them as normal assigns and in doing so unnecessarily increase the memory burden (redundantly with respect to some of the original data structures) OR recompute the assigns twice per cycle from the data structures pre-change and post-change to compare them.
Maybe I got it wrong and there’s a neat way of doing this which I am not aware of, but seems like the concept of assigning only the changed component instances implies doing the assigns diffing ourselves, doesn’t it?
Thanks
The update callback in the component has both the new assigns and the current ones.
Thanks, Jose!
Will it be ok then to assign all the components as temporary assigns and then to filter out those with the same new and current assigns in this update callback? If so, what exactly should the update callback return to instruct LV not to render the instance?
Or you had some other approach in mind?
Ok. I’ve just tried a number of things with the LiveComponent update callback. I manage to detect when there are no changes to apply and return the socket unchanged.
However, this does not help as it all happens after all the component instances are already instantiated, the end result being the same - if I assign all of them (i.e. if I don’t filter out the assigns before invoking the live_component macro), LV will include each of those component id’s in the array and send them empty to the client.
Is there some way to find out about the old (current) component assigns before invoking live_component macro in the template?
Thanks
Jose, do you think the timely LV exposure of the component old assigns can be planned for a future release?
you can do this in your app today:
<%= for component_arg <- @component_args do %>
<%= live_component @socket, FooBar, @component_arg %>
<% end %>
And then you keep a list of the components you want to render. That’s how the livedashboard works.
Thanks for the reply. I now need to wrap my head around this (i.e. take a deep look at the livedashboard code) for I must say at first it doesn’t ring a bell.
Let me see if I get this. The comprehension you give above is to illustrate passing through all of the component instances in order to get the update function invoked on each instance so that it (the update function) can assign to the socket which particular components to render.
If that is correct, I miss how/where I can render an isolated comprehension like that (run that code) to get the desired assigns in the socket and at the same time not have the result of such rendering actually rendered into my page.
The idea is that your LiveView would only include in the @component_args list the components it thinks needs to be updated.
I see. But that is the nature of the problem here. For my LiveView to only include those component args that should render it needs to compare the new ones with the old ones and to do that it needs to have the old ones stored somewhere meaning they can no longer be temporary assigns. And that would only add to the data I keep with the LiveView instance. I wanted to avoid that, for LiveView does keep my old assigns anyway for its own diffing purposes and all I need is to let me access those from my template code so I can chose whether to render a component instance or not.
Btw, this entire issue would not even exist if LiveView itself treated the case where no assigns have changed so as to skip the component altogether and not return the empty component slot with its id listed in that array that it sends to the client.
I guess you made it send that array with empty component slots for some purpose so I won’t argue about that, but then at least some way of accessing the old assigns at the template code level would be helpful to avoid that.
If a component is not changed on update
, then the diff is empty: phoenix_live_view/diff.ex at master · phoenixframework/phoenix_live_view · GitHub
And if the diff is empty, it is not included in the diffs: phoenix_live_view/diff.ex at master · phoenixframework/phoenix_live_view · GitHub
Now that’s interesting.
So, my component code (which is essentially redundant with what LiveView would do by default):
def update( assigns, socket) do
change? = Map.take( socket.assigns, Map.keys( assigns)) != assigns
socket = change? && assign( socket, assigns) || socket
IO.puts( "#{ inspect( socket.assigns[ :myself])} change?: #{ change?}")
{ :ok, socket}
end
Inspecting the diffs in your Diff.render_component code:
diffs =
if diff != %{} or new? do
Map.put(diffs, cid, diff) |> IO.inspect()
else
diffs
end
Therefore (the shell output):
..
%Phoenix.LiveComponent.CID{cid: 18} change?: false
%Phoenix.LiveComponent.CID{cid: 19} change?: false
%Phoenix.LiveComponent.CID{cid: 20} change?: false
%Phoenix.LiveComponent.CID{cid: 21} change?: true
%{
21 => %{
19 => %{
d: [
[387],
[388],
[389],
[390],
[391],
[392],
[393],
[394],
[395],
[396],
[397],
[398],
[399],
[400],
[401],
[402],
[403]
]
}
}
}
%Phoenix.LiveComponent.CID{cid: 22} change?: false
%Phoenix.LiveComponent.CID{cid: 23} change?: false
%Phoenix.LiveComponent.CID{cid: 24} change?: false
%Phoenix.LiveComponent.CID{cid: 25} change?: false
..
However (the JSON object):
{
"2": {
"5": {
"0": {
"d": [
[
4
],
[
5
],
[
6
],
[
7
],
[
8
],
[
9
],
[
10
],
[
11
],
[
12
],
[
13
],
[
14
],
[
15
],
[
16
],
[
17
],
[
18
],
[
19
],
[
20
],
[
21
],
[
22
],
[
23
],
..
To sum up, the shell output is 100% correct yet the JSON object in the browser still got the entire array of all component id’s not just the component #21.
Please note that if I assigned the component args for the component #21 alone, the array would naturally contain just the #21.
So, the question is what happens in-between the diffs = … and the construction of the JSON object sent to the client.
Thanks
I think the issue in your case is the list. The list is always rendered. So you need to use append-only or trick I shared to only render part of the list. We want to provide more robust list/collection helpers in the future but they are not available right now.
I agree, but in the meantime, could you just add some way to access the old (current) component assigns held by LiveView so I can access them from my template code based on their component id so I could effectively reduce this list that always renders to just 1 element i.e. filter out all the unchanged components from the comprehension?
Actually, no need for adding the interim feature. Just finished my myers difference based component list arg diffing algo that computes both the new and the old assignments per cycle for each component. 1.x ms for quite a large data set on my far-from-new machine.
Elixir is indeed fast!
Thanks and looking forward to those list helpers in LiveView.