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!

Last Post!

sodapopcan

sodapopcan

That monad thing is neat, though I personally don’t actually care that Elixir doesn’t have codified monads and kind of like that it doesn’t. Elixir hits this really nice mix of elegant and scrappy for me that not only makes it productive but also less gatekeep-y.

Where Next?

Popular in Proposals: Ideas Top

kccarter
This is likely a feature request unless we’re overlooking something, but it would be a nice improvement to the developer experience if th...
New
Jskalc
Hi everyone! Recently I was thinking a lot about the way HEEX renders lists. People are generally surprised about huge payloads being sen...
New
mortenlund
Hi! I would like to suggest a new callback in the lifecycle of the Live Component which is unmount. Sometimes it is nice to be able to ...
New
BartOtten
I’d like to propose that we refrain from using the term "DeadView" as the opposite of “LiveView” and instead choose an alternative. A new...
New
aglassman
Problem The cancel_async function is easily overlooked. Since the results of “outdated” tasks are ignored, it’s easy for developers to a...
New
engineeringdept
In 2026 double submit/session tokens are no longer necessary to prevent against CSRF attacks. Instead, we can use the Sec-Fetch-Site head...
New
cevado
I was reading the EEP-79, and thinking about the poor record support in Elixir(i’ve tried to discuss about that in the forum before). I s...
New

Other popular topics Top

rms.mrcs
Hi, I need to transform a list of numbers into a map where the keys are the indexes and the values are the original values of the list. ...
New
jononomo
I am trying to figure out how Mix knows whether the environment is test, dev, or prod – where is this set? Thanks.
New
New
dokuzbir
I want to highlight html closing tags when i click a html tag. That works in .html files but doesnt work for html.eex templates. How can...
New
gshaw
What is the idiomatic way of matching for not nil in Elixir? E.g., First way: defp halt_if_not_signed_in(conn, signed_in_account) when...
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

We're in Beta

About us Mission Statement