thiagomajesk

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_new version 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_merge function that takes a function or a value (a map) and merges it with the assigns that exist in the socket - we can also have an assign_new_merge counterpart.

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).

Showing Posts 1 to 10

sodapopcan

sodapopcan

My guess around assign_new only 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 using assign to store a function.

I don’t see a version of assign_new that 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 did assign_new(socket, :foo, fn -> "hello" end) store "hello" and not my function??” I believe that is called Principle of Most Surprise :wink:

Where are the places you are using assign_new a lot? I’ve only ever used it to store defaults which was made obsolete by attr. And defaults for live components can always be done in mount/1. I’m interested in your usecase regardless!

thiagomajesk

thiagomajesk OP

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 attr is 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 support required and defaults yet).

For this game I was developing, I had fewer components that needed something like assign_new but I used assign_merge a 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_new on 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 using assign_new to process expensive stuff just once, so I was using that a lot too

RudManusachi

RudManusachi

about assign_merge

Currently assign/2 allows to pass a map or keyword list:

A keyword list or a map of assigns must be given as argument to be merged into existing assigns.

though, it doesn’t have a “lazy” counterpart that would take a function..

sodapopcan

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 attr need 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

thiagomajesk OP

The problem I guess is that since mount is called before and after the socket has connected, if you do just assign, 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 for assign_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

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_async in conjunction with testing for connected?(socket).

benwilson512

benwilson512

Author of Craft GraphQL APIs in Elixir with Absinthe

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

sodapopcan

You can also do:

socket =
  if connected?(socket) do
    assign(socket, :foo, App.get_foo())
  else
    assign(socket, :foo, nil)
  end

no? Or are you making another point I am missing?

benwilson512

benwilson512

Author of Craft GraphQL APIs in Elixir with Absinthe

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

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 :upside_down_face:

Where Next? Top

Trending in Discussions Top

AstonJ
As the title says, please share what you’ve been up to with Elixir. Whether that’s been learning it, looking into it, making stuff with i...
2977 94592 917
New
cblavier
Hey there, It’s been more than a year since we started using LiveView as our main UI library and building a whole library of UI componen...
New
mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
heathen
Quite interesting article Google brought me. Didn’t find any mentions about it here. What do you think in general? Would you use togethe...
New
mhanberg
Hi everyone! The first release candidate for the Expert language server project is now available! We’ve published a press release detai...
New
axelson
Hi there! :wave: @frigidcode and I (but mostly him) have been running an Elixir Book club, we’re almost done with Designing Elixir Syste...
New
budgie
A little off-topic, but I feel like people here have a good head on their shoulders. I used to be quite good at making software. Was luc...
New

Other Trending Topics Top

GenericJam
Edit: 2026 May 15 - This post is archived. Mob is alive!! Main docs: mob v0.7.11 — Documentation A bit of explanation for the slightly c...
New
JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
New
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
georgeguimaraes
Just published claude-code-elixir, a plugin marketplace for Claude Code with Elixir support. These are the plugins I’ve been using for my...
New
Dmk
Xamal is a deployment tool for Elixir apps that deploys native releases to bare metal servers over SSH. It’s a port of GitHub - basecamp/...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews