#[LiveView] How to return partial data (stream)?

14 messages · Page 1 of 1 (latest)

tulip scroll
#

Hi!
Right now I have a binding phx-click that fetches data from an external API, it does with pagination, you can see its implementation here: https://discord.com/channels/269508806759809042/1068990798055608491

Instead of returning all data at once, I would like to update the LiveView assign with partial data from each pagination.

It is this possible?

I already tried to just call assign(socket, :items, items) but it doesn't do anything.

Thanks!

polar night
#

assign is the right thing to do, can you show the code that is calling assign?

tulip scroll
#

@polar night

def getNewBatch(count, after_id, items, socket) do
    case HTTPoison.get(
           "https://oauth.reddit.com/user/username/saved",
           params: [
             {"count", count},
             {"after", after_id}
           ]
         ) do
      {:ok, %HTTPoison.Response{status_code: 200, body: body}} ->
        {:ok, decodedJson} = Jason.decode(body)
        new_count = decodedJson["data"]["dist"]
        new_after_id = decodedJson["data"]["after"]
        new_items = decodedJson["data"]["children"]

        assign(socket, :items, items)

        if(new_after_id != nil) do
          getNewBatch(new_count + count, new_after_id, [new_items | items])
        else
          items
        end
    end
  end

def handle_event("get_items", socket) do
getNewBatch(0, nil, [], socket)
{:noreply, socket)
end
polar night
#

@tulip scroll Elixir is immutable. Updating data structures in elixir does not mutate them in-place like you might expect in other languages. Try this in an iex shell:

iex(1)> my_map = %{foo: "bar"}
%{foo: "bar"}
iex(2)> Map.put(my_map, :foo, "updated!")
%{foo: "updated!"}
iex(3)> my_map
%{foo: "bar"}

You can see that the variable my_map that has a map in it, retains the old version of the map, even after I called Map.put to update it. This is because Map.put does not mutate the map in-place. It updates the map and returns a new copy with the update. To use it, you have to capture it in a variable:

iex(4)> my_map = %{foo: "bar"}
%{foo: "bar"}
iex(5)> my_map = Map.put(my_map, :foo, "updated!")
%{foo: "updated!"}
iex(6)> my_map
%{foo: "updated!"}

This is the same with the socket data structure in your code and the assign function. It's assigning the new values into the socket, and returning an updated socket.

#

Your get_new_batch function needs to return the updated socket. e.g.:

def handle_event("get_items", socket) do
  socket = get_new_batch(0, nil, [], socket)

  {:noreply, socket}
end
#

And your get_new_batch function needs to bind the updated socket as well. e.g. where you use assign, you have to do this:

socket = assign(socket, :items, items)

and then you have to make sure to return the new socket at the end of the function.

#

This would be a lot easier if you refactored things a bit, the recursive function should probably be just responsible for fetching items and returning them, and then you can easily assign them to your socket instead of doing the assignments inside the recursive function. e.g.:

def handle_event("get_items", socket) do
  items = get_new_batch(0, nil, [])
  
  socket = assign(socket, :items, items)

  {:noreply, socket}
end
tulip scroll
#

@polar night thanks a lot! Your final code is what I have right now, fetch all 1000 items recursively and return them, but I want to return them as they come, for example each 25 items update the assign, so the user see them as fast as possible without having to wait for the entire 1000 items

polar night
#

You'd have to use messages to do async work, like instead of immediately fetching the next page, you should update the socket and return it, but before returning send the process a message using send, and then write a handle_info callback to handle it.

#

It's possible to do, just a bit more complicated and requires understanding of OTP processes and GenServers. Have you read the getting started guide on OTP?

tulip scroll
#

I haven’t, will read it, thanks again!

polar night
#

Remember that anything you read and learn about GenServers applies to LiveView precesses, because they are built on top of GenServers.