I’m trying to put together a view that lists the items in the db. There are a lot of items so I ended up setting up a pagination system that adds more items to the list. But now on mobile it has this jerk when I scroll down.
I tried to capture that in a video, not sure I succeeded, here it is: iCloud Photos - Apple iCloud
I tried to implement pagination like I saw on the Phoenix documentation v1. I modified it to not replace the items but to keep adding to the list.
Any hunches on what is going on here and what I can do to improve it?
A few questions:
- I’m not sure I’m using Phoenix.streams the correct way. Should I not even paginate, just hand the stream the entire list?
- I thought the problem was caused by the fact that the parent element didn’t have
phx-update=“stream”
, but I added it and I still have this issue.
defmodule PluginProphetWeb.PluginLive.Index do
use PluginProphetWeb, :live_view
alias PluginProphet.Plugins
alias PluginProphet.Plugins.Plugin
alias PluginProphet.Contributors
@per_page 50
@impl true
def mount(_params, _session, socket) do
contributors = Contributors.list_contributors() |> Enum.reject(&(&1.name in ["", nil]))
{:ok,
socket
|> assign(page: 1, per_page: @per_page, loading: false)
|> assign(
:form,
to_form(%{"search" => "", "search_fields" => ["name"], "contributor_id" => ""})
)
|> assign(:contributors, contributors)
|> stream(:plugins, [])}
end
@impl true
def handle_params(params, _url, socket) do
type =
case params["type"] do
"free" -> :free
"paid" -> :paid
"all" -> nil
_ -> :paid
end
{:noreply,
socket
|> assign(type: type)
|> apply_action(socket.assigns.live_action, params)
|> load_plugins()}
end
@impl true
def handle_event("next-page", _, socket) do
{:noreply, socket |> update(:page, &(&1 + 1)) |> load_plugins()}
end
@impl true
def handle_event("search", params, socket) do
{:noreply,
socket
|> assign(page: 1)
|> assign(:form, to_form(params))
|> stream(:plugins, [], reset: true)
|> load_plugins()}
end
defp apply_action(socket, :edit, %{"id" => id}) do
socket
|> assign(:page_title, "Edit Plugin")
|> assign(:plugin, Plugins.get_plugin!(id))
end
defp apply_action(socket, :new, _params) do
socket
|> assign(:page_title, "New Plugin")
|> assign(:plugin, %Plugin{})
end
defp apply_action(socket, :index, _params) do
socket
|> assign(:page_title, "Listing Plugins")
|> assign(:plugin, nil)
end
defp load_plugins(%{assigns: assigns} = socket) do
%{type: type, page: page, per_page: per_page, form: form} = assigns
search = form.params["search"]
search_fields = form.params["search_fields"] || ["name"]
contributor_id = form.params["contributor_id"]
search_fields = Enum.map(search_fields, &String.to_existing_atom/1)
opts = [
search: search,
search_fields: search_fields,
type: type,
contributor_id: contributor_id,
page: page,
per_page: per_page
]
socket = assign(socket, loading: true)
{total_count, plugins} = Plugins.list_plugins_and_total_count(opts)
socket
|> assign(loading: false, count: total_count)
|> stream(:plugins, plugins, at: -1)
end
end
The page
<div class=" min-w-min" phx-update="stream" id="plugins">
<.table
rows={@streams.plugins}
pagination
id="plugins_table"
row_click={fn {_id, plugin} -> JS.navigate(~p"/plugins/#{plugin}") end}
>
<:col :let={{_id, plugin}} label="Name">
<p class="w-[240px]">
<%= plugin.name %>
</p>
</:col>
<:col :let={{id, plugin}} label="Total installs">
<p>
<span id={"usage_#{id}"} phx-hook="FormatNumber"><%= plugin.usage_count %></span>
</p>
</:col>
<:col :let={{id, plugin}} label="in production apps">
<span id={"paid_installs_#{id}"} phx-hook="FormatNumber"><%= plugin.app_count %></span>
</:col>
<:col :let={{_id, plugin}} label="Maker">
<span :if={plugin.contributor && plugin.contributor.name}>
<%= plugin.contributor.name %>
</span>
</:col>
</.table>
</div>
And the search function
"""
@spec list_plugins_and_total_count(plugin_list_options()) :: {non_neg_integer(), [Plugin.t()]}
def list_plugins_and_total_count(opts \\ []) do
base_query = PluginQueries.build_base_query(opts)
base_query = PluginQueries.apply_contributor_filter(base_query, opts)
search_query =
base_query
|> PluginQueries.apply_search(opts)
total_count =
search_query
|> exclude(:order_by)
|> exclude(:preload)
|> exclude(:select)
|> select([p], count(p.id))
|> Repo.one()
plugins =
search_query
|> PluginQueries.apply_sorting(opts)
|> PluginQueries.select_fields(opts)
|> preload(:contributor)
|> PluginQueries.apply_pagination(opts)
|> Repo.all()
{total_count, plugins}
end