jclem

jclem

Phoenix LiveView Server & Client State

Let me preface this by saying I’m a huge fan of LiveView—this discussion is borne purely out of love for LiveView and respect to everyone that works on it. Using it is an amazing experience!

Frequently when I’m working on something new, I have major decision paralysis where LiveView is involved, and it often comes down to just the need to render something like a modal. Usually the thought process goes like this:

  1. Find a LiveView Modal example or use the mix phx.gen.live generator
  2. Remember that modals whose open/closed state as in the generator and most blog examples result in what I consider to be sub-optimal UX (In poor network conditions or even when a random network blip occurs, what users typically expect to be an instant interaction suddenly has noticeable and frustrating latency associated with it)
  3. Find examples that use LiveView but show/hide the modal using JavaScript
  4. Realize the only JS inter-op that works well so far (that I’ve found) is Alpine.js, but using Alpine.js has security implications I’m uncomfortable with (I want to avoid a CSP with script-src 'unsafe-eval', which Alpine.js requires)
  5. Worry about having this debate for every component and wonder whether to just avoid the issue altogether and step back into the more familiar territory (for me) of a Phoenix API + React via Next.js, for example.

Even if CSP weren’t a concern, using a client-side library like Alpine.js still comes with a set of problems I’m not sure how to solve. Consider the following flow:

  1. User shows a modal containing a LiveView-rendered form via Alpine.js
  2. User submits the form, but errors come back and are rendered via LiveView
  3. User changes their mind, closes the form via Alpine.js
  4. The app sends a WebSocket frame to tell LiveView to clear form state
  5. User changes their mind again, re-opens the form, but the “clear the form” message is still in transit, and the user sees their old errors, which then unexpectedly disappear when the “clear the form” message is finally processed and the modal contents re-rendered

Of course, this is a very specific and somewhat contrived example, but I think it illustrates the general theme that without careful consideration, LiveView can take you from dealing with the already-difficult problem of managing a single UI thread to dealing with a second UI thread for certain pieces of state.

The answer here just may be that LiveView is specifically not the right solution for UI elements like modals and other similar UI elements right now, but then having to build an app where some components work via LiveView like a single page with a form on it, and others require separate templating/state management/etc. on the client seems to add additional complexity that I would avoid with an architecture that has a single source of UI state.

I’m curious if others have thought about this, and especially if you’ve got an app in production or development using LiveView heavily, how are you dealing with it?

Most Liked

benwilson512

benwilson512

Author of Craft GraphQL APIs in Elixir with Absinthe

Mostly, we just do the regular live view flow, and it works fine, even all the way across the world. Our servers are located in US east coast, and pings from Singapore are a little slow, but even still it isn’t actually slow enough to actually influence people’s workflows. (Caveat, we are B2B software not B2C, which may be more sensitive to such things).

Beyond the “don’t worry about it” response though, my company’s particular architecture allows for a very interesting model I’m hoping to POC soon: Geographically distributed live view servers. At CargoSense, our LiveView nodes are do not touch the database, rather they make GraphQL calls back to our platform nodes when they need data. This means that we could be distributing our UI layer nodes all over the world and then UI interactions that merely open a modal or don’t otherwise hit the DB just have to do a loop through the nearest liveview node instead of travel all the way to us-east4.

f0rest8

f0rest8

Hopefully v3 of Alpine will address the CSP/security concerns (and maybe more?), as mentioned here.

tj0

tj0

Longtime lurker (pretty sure I have another account which I couldn’t find).

I feel your pain.

I was originally using alpine for the modal from the following guides:
Creating LiveView Modals with Tailwind CSS and AlpineJS
Create a reusable modal with LiveView Component · FullstackPhoenix

I got it working, but it felt a bit messy. I finally settled on storing all the modal open/close state in a checkbox on the client-side and everything else on the server-side. No need for any client side js.

Wrote it up in more detail for posterity here:
No Javascript Clientside Phoenix Live View Modals

Last Post!

LostKobrakai

LostKobrakai

I’m not sure this is a good take. If the expectation is client side functionality then it needs to be in js (or wasm, …). I see the benefit in Phoenix.LiveView.JS on a similar level as e.g. stimulus. Driving client level functionality, which is implemented somewhere in the js. Glueing together JS functionality with where it’s meant to be applied.

There’s for sure a place where you have so much client side only interactivity that it no longer makes sense to have LV involved, but I rarely work in that area.

I also prefer the simplicity of non live views, but with heex being part of pheonix_live_view anyways and it being usable in both it’s no longer a hard jump.

Where Next?

Popular in Questions Top

skosch
To my knowledge, put_in, Map.update etc. all have the one limitation of not automatically creating intermediate keys when needed (for exa...
New
lanycrost
Hi everyone! I need implement if…else if…else condition from my elixir code, and anymore of this control flow structures not work proper...
New
mcarvalho
What is the difference between System.get_env and Application.get_env? For example, what are best practices to use one versus another.
New
stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
Darmani72
If I have a post route which an argument: post /my_post_route/:my_param1, MyController.my_post_handler How would get the post params ...
New
vrod
I am using the Starship cross-shell prompt – it seems pretty nice, but I get some errors: [WARN] - (starship::utils): Executing command ...
New
JorisKok
I have a server on AWS, and was running a load test using artillery. When looking at the Phoenix dashboard I see the Ports going to 100% ...
New

Other popular topics Top

jononomo
I am trying to figure out how Mix knows whether the environment is test, dev, or prod – where is this set? Thanks.
New
vonH
When I run the Plug and I recompile I wind up having to use Ctrl C to quit iex and start again. Witht the help of rlwrap I can use the cu...
New
lanycrost
Hi everyone! I need implement if…else if…else condition from my elixir code, and anymore of this control flow structures not work proper...
New
saif
Hello everyone, Long time lurker first time poster here. I’ve recently begun working on Elixir full-time again! :raised_hands: It’s been...
New
WestKeys
Currently suffering from paralysis by [HTTP client] analysis. This is rather unusual in Elixirland as there tends to be consensus on the ...
New
TunkShif
This post is an instruction guide to help you setup your Neovim for Elixir development from scratch. It includes general information on h...
274 42576 114
New

We're in Beta

About us Mission Statement