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 ![]()
Most Liked
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
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/3can also return{:ok, state, keyword}handle_call/3can also return{:reply, term, state}handle_event/3can 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
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!
Popular in Proposals: Ideas
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #advent-of-code
- #elixirconf-us
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #performance









