Each LiveView
has it’s own process, and the process gets killed when navigating away from it, like your example shows. The only way to keep some state between them is to persist it in another process.
That makes sense I guess
I misunderstood from Chris’s message here that there is an architecture such that makes the process survive the navigation
<%= live_render(@conn, MyChatLive, ...) %>
in your root layout would give you an isolated LV that survives live navigation of the main LV from thelive
route. We replace everything inside the main LV on navigation, so anything outside of it won’t be touched.
But that’s not what he meant probably?
Right, that’s not what he meant. He was saying that the process for MyChatLive
would survive navaigation because it’s in root.html.leex
. It has nothing to do with the socket being shared across multiple LiveView
s while navigating.
Ok so I my understanding gap is bigger than I thought :this-is-fine-meme:
The way I reasoned about it is that each LiveView is and individual process (as usual) whose internal state
includes a socket
structure. Hence if rendering it at root.html.leex
allows it to survive routes navigation, I figured I could use it as a source-of-truth socket
for all the rest of LVs to consume/write-to.
In that case - if it’s not a socket - can I utilize its features to share state all across the “child” LVs?
If so:
- How does its
mount
look like? ie. it seems to includesocket
in its params - How does its
render
look like?
Thanks truly
That’s where your thinking starts to go down the wrong path. Remember that LiveView
is built on top of the functionality Phoenix.Socket
and Phoenix.Channel
provide. Your socket endpoint is defined in AppWeb.Endpoint
. Each LiveView
process is a channel and the client subscribes to the topic. If you have window.liveSocket = liveSocket
in your app.js
, run liveSocket.enableDebugging()
in your browser’s console. You can see LiveView
s subscribe to their topics and diffs come over the wire when socket.assigns
changes.
All that being said, you can’t share a socket with different LiveView
s. You can, however, use stateful and stateless LiveComponent
s to pass down assigns to children, similar how you would pass down props
to a React child.
Thanks!
So based on your insights and what’s at Slack I tried to then place a LiveComponent at the root.html.leex
to act as a common source of truth which forwards data to all “child” LiveViews.
Something like
# root.html.leex
<%= live_component @socket, TruthComponent do %>
<%= @inner_content %>
<% end %>
Where
defmodule TruthComponent do
use Phoenix.LiveComponent
def mount(socket) do
IO.puts("TruthComponent mount")
{:ok, assign(socket, :myvalue, 120)}
end
def render(assigns) do
~L"""
<%= @inner_content.(myvalue: @myvalue) %>
"""
end
end
Just to test if PageLive
and SecondLive
would be able to access myvalue
.
Unfortunately I cannot tie it up together properly at all.
Also architecting in a way were the LiveComponent
is the parent and the LiveView
s are its children that receive info from it feels like I’m messing up the roles - and that it should be organized
LiveView -> LComponent
not viceversa.
keep some state between them is to persist it in another process
Hence your initial suggestion sounds like what I should be aiming at, a separate dedicated OTP process that would allow one to keep&share state from/to all LVs. It feels like i’m swimming against LV/LC’s flow fundamentally.
Regardless I’ll be happy to learn from some implementations/snippets if someone landed the aforementioned approach
Hence your initial suggestion sounds like what I should be aiming at, a separate dedicated OTP process that would allow one to keep&share state from/to all LVs.
I think that’s the way to go.This way, not only can you share state across live views, but you can easily share it across your entire app.
Check out this thread: Channels - Where to store state? for some details on how you might go about it. Specifically, this post by @sasajuric describes a couple straightforward approaches.
Nope, not possible You can have a single
LiveView
pass down assigns to LiveComponent
s, but that’s it. I think we are starting to go in circles here, maybe let’s shift focus. What are you trying to accomplish?
Thanks @Most as well for the informative link
Nope, not possible
Yep I think that sums it up, I’m probably trying to do something with these tools which misses their very design currently.
The final real use-case in my case is basically as follows; conceptually an e-commerce with a shopping cart:
- Cart value is kept in browser’s
localStorage
so user can resume on next visit, which is sent upon page landing via JS
let liveSocket = new LiveSocket("/live", Socket, {params: {
_csrf_token: csrfToken,
cart: localStorage.cart
}})
-
1 x LiveComponent
CartComponent
that keeps the state of the cart & all its handlers (add-item
,remove-item
, etc) and receives the localstorage copy from parent LV uponmount
->render
. Also sends updates to parent LVs when cart events arrive; hence being the source of truth for the cart. -
1 x LiveView
Home
. Makes use ofCartComponent
at the top and can add/remove to it. -
1x Liveview
ItemPage
. Makes use ofCartComponent
at the top and can add/remove to it. -
1x LV
Checkout
. Makes use ofCartComponent
to list items and add/remove to it.
Conceptually I realized I was looking for something similar to an in-memory router (~ to react-router
's SPA MemoryRouter
) but on the server .
That way I only have to resume once from localStorage (maybe even checking against a persistent copy of the cart in the backend), and from then on all the navigation from LV to LV just keeps that cart in memory.
Thanks tho, this was very helpful!
Hey man
I just wrote a very lengthy reply to another thread rambling about how we are using LV in our company right now…
I think I should have rambled about it here, but instead please read this (skip ahead if you are not interested)
thank you!
I was finding myself saying “yes! this!” while reading your post
Really happy to see more advanced phoenix LV users like yourself already solving these types of patterns; and do let me know if I can help somehow in that effort.
I am just starting to understand what I wanted really so all the necessary pieces/roles are not yet clear in my mind. But I’ll give it further thinking cause I’d love to use an architecture like that
One question I had at first is how to point all URL to the same entry-point(ie master LV/source-of-truth) but I reckon one can pattern-match to it as a route? As per state, live-store felt like a good starting point to part of the solution.
What stage are you at to land that pattern?
We are already using it in production, to great success.
Maybe that was a little misleading, you can use the LV routing - you just need different “routers” or “parents” for the routes, because you probably want to render different templates/LVs.
So the “Router” is just the parent LV for a routed (in phoenix router) URL.
Oh wow, congrats then
for a non-business-critical open-sourcing of the logic someday : D
Ok both of those together make my head explode a little bit, I need to give it more thinking.
I was imagining something like
# at router.ex
live "/:route", RouterLive
and then
# At RouterLive module
def mount (...) do
# Load some stuff from the landing request
end
def handle_params(%{"route" => route },...) do
{:noreply, assign(socket, :route, route)}
end
def render(assigns) do
~L"""
<%= if @route =="first-page" %>
<%= live_render @socket, FirstPageLive, ... %>
<% else %>
<%= live_render @socket, SecondPageLive, ... %>
<% end %>
"""
end
# <-- Then here all the handle_event thingies from stateful LiveComponents
Is that even close?
Close enough - the routing is done by LiveView, and they provide the live
route.
So lets say you have a simple twitter clone - called twooter. You want a “twoots” route as index to all twoots the user is subscribed to, and an “accounts/:account_id” route to change settings.
live "/twoots", TwootsLive
live "/accounts/:account_id", AccountsLive
TwootsLive
and AccountsLive
are your parents (the longer I talk about this, the less I think we should call it Router, maybe LiveRoute
)
TwootsLive
takes care of the params, and renders a template, same as AccountsLive
.
TwootsLive is a twoots manager, you can have X amounts of children, as long as you subscribe them to the parent (which makes the initial loading a little slower, because all those LVs have to mount, but browsing on the “twoots” page would be fast, because it’s processes telling each other about state change, loading their new state concurrently.)
AccountsLive would look very different, it could be only a single subscribed LV, to edit your password for example - in this case, I’d even say, don’t use a nested LV, just do everything in AccountsLive
Oh I see so if I understood your example correctly the AccountsLive
in your use-case is somewhat unrelated to our discussion, right? You mentioned it for contrast.
Whereas your TwootsLive
would do something similar to my RouterLive
above? With the use-case difference that each of TwootsLive
's child is of the same “type”(say SingleTwoot
), whereas at RouterLive
each of the children is a completely different “page”/LV, which in any case is not very relevant beyond the product perspective.
Good points on mount-time performance (not sure how i’d measure it yet, but good to keep in mind) and also making sure to subscribe all children LV to their parent LV.
Thanks!
I guess I should clarify that most apps probably would not need to use this pattern - you can create perfect apps with just one liveview, maybe using live components… We just started using this pattern because we had complex logic on different pages, but wanted to be able to drop in all those parts on different pages - mix and match if you want.
Sounds like what I’m searching for. Do you think it’s possible for you to setup a very small example project?
I am not sure - what would be the specs? You can create LVs with children and make them subscribe to parents, and send updates via parents - but I am not sure that is what you want, you might want a more simple solution with components?
Seems like Phoenix LiveSession might be similar to what you guys are thinking? Or maybe could provide inspiration? It uses GenServer, PubSub and an ETS table. The canonical use case is a shopping cart.
@wmnnd, the author, introduced it on this forum here.
I was about to suggest the same thing. A central session store to share state would solve most of the complexity.