thiagomajesk
Hi everyone.
Some time ago when I was doing some heavy work with LiveView for a game I was developing (now paused) I noticed a pattern that I kept using over and over again in other projects to clean up code.
I’ve been meaning to push some contributions to LV for quite some time, but I don’t know what the protocol to accept changes like this are. So I’d like to know, specially from @chrismccord if this is something desirable to have in LiveView. Here’s some of the helpers I’ve been constantly reusing in projects:
- An
assign_newversion that supports passing just a single value:
If you have lots of assigns in your LiveView, chances are that you have used this a lot before, so it might be useful to have another version that also allows you to pass values to it: assign_new(socket, :title, "Hello World") instead of the more verbose one that takes a function assign_new(socket, :title, fn -> "Hello World" end) and does the same.
- An
assign_mergefunction that takes a function or a value (a map) and merges it with the assigns that exist in the socket - we can also have anassign_new_mergecounterpart.
Time and time again I saw myself using this helper because I had functions that returned the initial (or updated) values for the LiveView state and I had to reassign the values by hand every time. It works like this: assign_merge(socket, :state, %{title: "Hello World"}) or assign_merge(socket, :state, fn -> %{title: "Hello World"} end).
One might argue that the abstraction is too thin, since we have the Enum module functions, but I think it not only makes you write less, but it adds a lot of readability to your code, specially when you have lots of assigns in a pipeline.
PS.: On a side note: I always thought about the assigns functions like the ones we have on the Map or Keyword module like put, put_new and put_new_lazy but LV does not follow this convention (probably for some good reason). I don’t know if this was discussed before or if keeping “compatibility” was ever intended but if we were to have a breaking change, it should be considered before 1.0 (if anyone knows the answer for this, please share).
Trending in Discussions
Other Trending Topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #library
- #deployment
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #elixirconf
- #channels
- #exunit
- #discussion
- #code-sync
- #podcasts
- #javascript
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #ai
- #elixirconf-us
- #blog-post
- #elixir-ls
- #phoenix_html
- #iex
- #graphql
- #genstage
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #elixirconf-eu
- #metaprogramming
- #hex











Showing Posts 1 to 10- Show Best Posts
- Show All (oldest first)
- Show All (newest first)
sodapopcan
My guess around
assign_newonly accepting a function is that since there is likely no good reason to ever store a function as an assign, it’s more concise—especially for framework code—to just have the single higher order function. It’s also kind of self-documenting to say, “Hey, there’s no good reason to store a function in assigns!” Highly speculative, of course, since there’s nothing stopping you from usingassignto store a function.I don’t see a version of
assign_newthat took a non-function arg being accepted because now you have a function that looks like it just assigns stuff but gives functions special treatment. This means it would have to be more verbosely documented and there would probably still be a steady enough stream of people asking: “Why didassign_new(socket, :foo, fn -> "hello" end)store"hello"and not my function??” I believe that is called Principle of Most SurpriseWhere are the places you are using
assign_newa lot? I’ve only ever used it to store defaults which was made obsolete byattr. And defaults for live components can always be done inmount/1. I’m interested in your usecase regardless!thiagomajesk
Good point! I see how this could be confusing if we have both options under
assign_new. In that case, making the distinction by having different functions would be better (eg:assign_new_lazy), but now we are talking about introducing a breaking change to the API (still doable though).There are still some cases where
attris not enough, especially when you are dealing with computed assigns (e.g.: if are doing stuff like class variance authority) or when you have more complex slots (IIR attrs in slots don’t supportrequiredanddefaultsyet).For this game I was developing, I had fewer components that needed something like
assign_newbut I usedassign_mergea lot… I had GenServers representing part of the game world and they were responsible for holding the game state (like player actions and such). Because LiveViews acted as “windows” into what was happening in the GenServer, they were always receiving communication back from those GenServers with new information that had to be merged back, usually in the shape of maps with many assigns.Edit.: I thought about another use-case after posting, which is using
assign_newon an LV mount to compute stuff just once… I’m not sure if I remember this right, but since LV is mounted twice, I guess I was usingassign_newto process expensive stuff just once, so I was using that a lot tooRudManusachi
about
assign_mergeCurrently
assign/2allows to pass a map or keyword list:though, it doesn’t have a “lazy” counterpart that would take a function..
sodapopcan
That’s interesting, I never thought to do that. If the computation is still going when the socket connects, do you have to handle that scenario? Will it trigger a re-render in that case when it does complete? I’ve never developed a game so it’s that would be full of considerations I’ve never, uh, considered, ha. That case does seem like a good fit for the upcoming
assign_async.I’ve never had
attrneed anything more than a simple value. I’ll have to look up class variance authority as I have no idea what it is so definitely not in a position to comment!thiagomajesk
The problem I guess is that since
mountis called before and after the socket has connected, if you do justassign, you’ll be processing the value twice (going to the database two times for instance). I’m not sure if it’s the same use-case forassign_async, which seems more useful for slower computations (like you mentioned).(For this game in particular, the computations are not usually too complex and don’t take longer than some ms, but I still have to do lots of queries to retrieve the state of game objects from the db, and some of those depend on others to be computed and so on).
sodapopcan
As is typical of me I either neglected to express some thoughts or was careless while deleting sentences in an attempt to be less verbose. I was imaging using
assign_asyncin conjunction with testing forconnected?(socket).benwilson512
This is unavoidable, and does not change by using assign new. The static connection and the live connection do not share values at all, they may not even happen on the same elixir server.
Where assign new helps is that in the case of the static render, you may have assigns already set from your plug pipeline. Assign new lets you reuse those on the static render, and then forces you to tell Phoenix how to fetch that same assign for the live render. This helps you fetch it only the 2 times and not 3 (twice in the static render, once in the live).
The only way to avoid fetching things twice is to use live redirect between pages so that you skip the static render entirely.
sodapopcan
You can also do:
no? Or are you making another point I am missing?
benwilson512
Yes sorry, I was not as precise as I could be. The mount itself is unavoidable, but you can condition on connected to avoid loading stuff in the static mount. Mostly what I mean is that if you need a value on both the static and live renders then it will necessarily be fetched twice, assign new doesn’t change that.
sodapopcan
Right, yes, I think I introduced confusion by not directly asking if @thiagomajesk required it on static mount and sort of ran with “maybe” while trying to answer