sezaru

sezaru

The update_many/1 callback let’s you make your update call more efficient by handing all update calls from the same component type all at once.

The documentation shows an example where instead of doing a query to the DB for each component, you do it only once inside the update_many/1 callback.

That’s great, but the DB call is still a blocking operation in the LV, meaning that if this query is slow, the LV component will also not finish loading until the callback is done.

The “obvious” solution for that would be to leverage LV’s async functionality by using start_async or assign_async, that way I can make the DB call not block anymore.

The issue now is that I’m back to doing one DB call for each async operation.

I can’t see how to still keep one DB call that update_many/1 allows but doing it without blocking the component.

The only way I can see that being possible right now would be to start a new process in the update_many/1 callback, and inside of it call a bunch of send_update for each component with its reply… Something like this:

def update_many(assigns_and_sockets) do
  ids =
    Enum.map(assigns_and_socket, fn {%{id: id, component_id: comp_id}, _socket} ->
      {id, comp_id}
    end)

  Task.start(fn ->
     results = ids |> Enum.map(fn {id, _} -> ids end) |> DB.get!()

    Enum.each(results, fn {id, comp_id} ->
      send_update __MODULE__, id: com_id, result: results[id]
    end)
  end)

  Enum.map(assigns_and_sockets, fn {assigns, socket} ->
    {assign(assigns, loading?: true), socket}
  end)
end

It is missing the code to handle the send_update message, but this should work. But doing something like this just feels wrong since I’m kinda just “re-implementing” the async operations (and, at least with the above code, without its great guarantees).

Why is this not already built-in in LV’s api? Or, if it is, where can I read more about it?

Showing Posts 1 to 6

mayel

mayel

I’m currently doing something similar to your example, and the downside is that it makes testing tricky since can’t use render_async, so keen to learn a better approach as well…

sezaru

sezaru OP

I don’t link to bump my own post, but I feel that this issue is not something too obscure to just be ignored.

It feels to me like an oversight from LV to not have a proper way to handle this use case. Or is there something in the architecture that makes this not so easy to fix in the first place?

drikanius

drikanius

Funny enough, a few weeks ago I fell into the same rabbit hole. I would really love a more interesting approach to this problem

codeanpeace

codeanpeace

Maybe I’m missing something but if memory serves, it should be possible to use start_async and handle_async from within a LiveComponent. Not sure if I like this approach personally, but maybe try calling start_async from within update_many instead of Task.start and then relaying the results to its sibling LiveComponents via send_update within handle_async?

def update_many([{%{loading?: false}, socket} | _] = assigns_and_sockets) do
  id_to_comp_id_mappings = 
    assigns_and_sockets
    |> Enum.map(fn {%{id: id, component_id: comp_id} _} -> {id, comp_id} end )
    |> Enum.into(%{})

  start_async(socket, :blocking_operation, fn ->
    id_to_comp_id_mappings
    |> Map.keys()
    |> DB.get!()
    |> Enum.map(fn result -> 
      {id_to_comp_id_mappings[result.id], result}
    end)
  end)

  Enum.map(assigns_and_sockets, fn {assigns, socket} ->
    assign(socket, :loading?, true)
  end)
end

def handle_async(:blocking_operation, {:ok, results}, socket) do
  # unlike from within `Task.start`, `send_update` does not need to explicitly pass 
  # the pid of the LV process as the first parameter to specify the target process
  for {comp_id, result} <- results do
    send_update(__MODULE__, id: comp_id, result: result, loading?: false)
  end

  {:noreply, socket}
end

# note that the `loading?` states might be different here since
# "The assigns received as the first argument of the update/2 callback 
#  will only include the new assigns passed from this function. 
#  Pre-existing assigns may be found in socket.assigns."
# https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html#send_update/3 
def update_many([{%{loading?: false}, %{assigns: %{loading?: true}}} | _] = assigns_and_sockets) do
  Enum.map(assigns_and_sockets, fn {%{result: result} = _assigns, socket} ->
    socket
    |> assign(:loading?, false)
    |> assign(:result, result)
  end)
end
sezaru

sezaru OP

That would kinda work, but it would make the first component responsible to all the changes which I’m not a fan. Also, it seems like it would be hard to handle errors.

sezaru

sezaru OP

So, in the end I decided to create a library that does this myself.

You can check it out here: LiveBulkAsync - A library to allow LV's async functionality in update_many function

It should has all the same guarantees that the LV’s async functions has (unless I messed something up :sweat_smile:)

— All posts loaded —

Where Next? Top

Trending in Questions Top

RSP87
I’m working on a project that simulates the bumbl example in the programming phoenix book. It acts almost like an email client. We have a...
New
nseaSeb
Hello, I know there is an approach for handling lists that allows for optimized traversal, but I can’t recall the specific method (somet...
New
brecabral
Documentation While reading the Scoped Routes section, I noticed that the documentation currently refers to a problem without explainin...
New
RemyXRenard
I’m seeing that a list inside a Kino.DataTable will be interpreted as a charlist, even if the Kino.configure() is set to charlists: :as_l...
New
velrest
So my question is quite simple and i have found no conclusive answer on forum, google or AI. Should we use :erlang.float for Integer to ...
New
asweet-confluent
I recently noticed that Elixir’s Logger defaults its primary log level to :debug when no :logger, :level application configuration is pre...
New
samoloth
Hi, I’ve just set up an application with ash_authentication. There is only magic link strategy for now, so there is no confirmation add o...
New

Other Trending Topics Top

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
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
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
mhanberg
Hi everyone! The first release candidate for the Expert language server project is now available! We’ve published a press release detai...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
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

Latest on Elixir Forum

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews