Aduril

Aduril

Reply-Function for LiveViews

Hello there,
Whenever I setup a new project, there is a small function I always add: reply/1. What does it do?
In a LiveView mount, handle_event or handle_info
instead of writing
{:ok, stream(socket, :posts, Blog.list_posts())}
or
{:noreply, stream_insert(socket, :posts, post)}

one should be able to write

socket 
|> stream(:posts, Blog.list_posts())
|> reply(:ok)

or

socket
|> stream_insert(:posts, post)
|> reply(:noreply)

Therefore, a function called reply/1 could be added into Phoenix. It could be implemented somewhat like that:
def reply(socket, reply) when is_atom(reply), do: {reply, socket}

This could increase the readability (it’s faster to “scan” the code of your LiveView).
You can put that little function in your project, but I would suggest to make it a part of LiveView itself.

I am eager to hear your opinion on that :slight_smile:

Most Liked

LostKobrakai

LostKobrakai

It’s not true that this is without value.

Those tagged tuples exist, because they do distinguish multiple distinct multi value return types.

:noreply is not the only tag you’re able to return from those functions. E.g. for handle_event may return {:noreply, Phoenix.LiveView.Socket.t()} | {:reply, map(), Phoenix.LiveView.Socket.t()}.

And even for the callbacks, which cannot currently allow multiple distinct tagged tuples that’s what allows them to evolve without breaking changes to eventually in the future have additional return tuples. E.g. handle_event didn’t always have the :reply tuple as a valid return type. It was eventually added once the integration with js hooks became a thing. That would’ve been a more complicated change if you would’ve been allowed to just return state without tagged tuple before.

Also this approach doesn’t really exist in isolation as well. The way those return values are structured are quite consistent between many different behaviours of various levels of abstraction backed by a process. You can look at GenServer, :gen_statem, GenStage, Broadway, Phoenix.Channel, Phoenix.LiveView and you’ll find that approach taken between all of them.

So this actually is achiving what you’re calling for:

LostKobrakai

LostKobrakai

I think this is where the mismatch comes from. Indeed in the case of {:noreply, state} or {:ok, state} it feels like you’re only transforming state. But there are in many places other options as well.

  • mount/3 can also return {:ok, state, keyword}
  • handle_call/3 can also return {:reply, term, state}
  • handle_event/3 can also return {:reply, map, state}

Those callbacks are not just transformations of state, but transformations of state is just one of potentially many things they do and return information about. Sometimes those other things are even the only thing happening with no changes to state.

E.g. for me most simple callbacks look like this:

def handle_event("something", _, socket) do
  socket = 
    socket
    |> assign(a: :something)
    |> update(:b, fn x -> x + 1 end)

  {:noreply, socket}
end

The state transformation is neatly contained in a pipeline, but the return of that state transformation is separate to the transformation itself. It doesn’t belong in the pipeline. This becomes apparent if the code changes and you need to return a reply:

def handle_event("something", _, socket) do
  socket = 
    socket
    |> assign(a: :something)
    |> update(:b, fn x -> x + 1 end)

  {:reply, %{b: socket.assigns.b}, socket}
end

:noreply is literally telling the caller of the callback “there’s no reply to send for this one”. That’s not a state transformation.

sodapopcan

sodapopcan

This has been talked about before a few times. I used to have have separate ok, noreply, reply, cont, halt functions but ultimately I found I preferred just using the plain ol’ bare tuples. I harp about “scannability” a lot and actually found this pattern to hinder it. With my eyes zooming through some code without actually reading any of it, I found it basically impossible to catch nested returns without a { poking out at me as the pipe version just looks like any other ol’ pipeline.

I don’t think this pattern is particularly bad for those who like it, but I don’t think it belongs in LiveView as it’s really easy for anyone to implement themselves. Some people also do this which doesn’t require any extra functions:

socket
|> assign(:foo, foo)
|> then(&{:ok, &1})

Also, you will want to to change it to reply/3 mount is also allow to return a 3-tuple!

Where Next?

Popular in Proposals: Ideas Top

jpbecotte
phx-hook accepts as argument an object where its methods are the lifecycle of the callbacks (mounted, updated, etc.). This is awesome, b...
New
mikesax
On a Rails/Turbo site, the first page is typically loaded using http GET and then sockets are used navigate and replace HTML content for ...
New
AHBruns
So, LiveView 0.20 brought an async processing API which is a thin wrapper around tasks. It’s a very nice convenience, but the main thing ...
New
pierrelegall
Problem Currently, List.first/2 and List.last/2 return a default value (or nil) when the list is empty. However, there are cases where an...
New
dvartic
Would there be interest to implement a link/1 that gives to option to operate on other events? So I implemented a simple link/1 function...
New
caslu
Recently (last week) this PR was merged in Ruby on Rails repository and i thought it could be nice to have in phoenix, basically what it ...
New
superchris
Currently there is no out of the box way to support DOM Custom Events in phoenix. I’ve created a separate library to do this, but I’d lov...
New
munksgaard
I’m copying and pasting this issue from @Terbium-135 here verbatim, because I would be interested in such a feature as well, and because ...
New
sodapopcan
So after complaining about this for the third or fourth time on this forum, I figured I should make a proposal. TL;DR with can be hard t...
New
alaadahmed
Hi folks, I tried Phoenix 1.7 and it is awesome, but I have small suggestion, which in my opinion will organize files in a better way. ...
New

Other popular topics Top

senggen
Erlang/OTP 25 [erts-13.2.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] 15:22:35.803 [error] gen_event {lager_file_backend...
New
lessless
I believe there are people here who are dealing with CSV files import on the daily basis, and since Excel is a really popular tool there ...
New
JeremM34
Hello, how can I check the Phoenix version ? Thanks !
New
fireproofsocks
Forgive me if this is obvious, but how does one delete a database record WITHOUT selecting it first? Ecto.Repo — Ecto v3.14.0 has exampl...
New
jerry
Good day to you all. I have been struggling to get a query involving like and ilike to work. Can anyone assist me on this, please? pro...
New
freewebwithme
Using vs code and installed ElixirLS: support and debugger. And I got an error popped up on start up says Failed to run ‘elixir’ comma...
New
shijith.k
I am trying to start a new phoenix project with elixir 1.9, but mix phx.new does not work. It says that ** (Mix) The task "phx.new" could...
New
joaquinalcerro
Hi there, I am working with Ecto-Postgresql and I need to call all of the records from a specific table but the table has 40,000 records...
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
vonH
In asking this question I am more interested about the expressiveness of the language itself and less concerned about the availability of...
New

Latest on Elixir Forum

Elixir Forum

We're in Beta

About us Mission Statement