What is the preferred way to implement pagination

Compared to the Rails where there are plenty easy to set up and use pagination gems (Kaminary, will_paginate to cite just some of them), it looks a bit surprising to not find a lot of similar libraries for Phoenix apps.

I took a look at existing ones in Elixir Toolbox, and tried to implement 2 of them:

Paginator was last updated in October 2022 and simply failed to install when running mix reps.get because of its lower dependency version of plug_crypto.
As for Flop Phoenix, I followed the installation and set up instructions for Flop, refactor the simple function listing companies as suggested:

def list_pets(params) do
    Flop.validate_and_run!(Pet, params, for: Pet, replace_invalid_params: true)
  end

but it also failed with the stack traces pointing to their library code. :face_with_raised_eyebrow:

I wonder what is the actual way of doing this kind of stuff, what is the preferred library to use or may be in most cases we have to implement the pagination in its own way?

but it also failed with the stack traces pointing to their library code.

Could you post the stack trace or open an issue?

Edit: Fix released with flop_phoenix 0.25.1.

1 Like

If you only followed the instructions from the Flop Phoenix readme, please follow the instructions in the Flop readme first: Flop — Flop v0.26.1.

Thank you for your response.
I used the versions you suggested. Here are the points popped up when using:

# mix.exs
defp deps do
...
      {:flop_phoenix, "~> 0.25.1"}
end
  1. The pattern match used in `handle_params/3 for LiveView section:
@impl Phoenix.LiveView
  def handle_params(params, _, socket) do
    {pets, meta} = Pets.list_pets(params)
    {:noreply, assign(socket, pets: pets, meta: meta)}
  end

is different from the one for LiveView Stream:

def handle_params(params, _, socket) do
  {:ok, {pets, meta}} = Pets.list_pets(params)
  {:noreply, socket |> assign(:meta, meta) |> stream(:pets, pets, reset: true)}
end

what results in error:

[error] ** (MatchError) no match of right hand side value: {[%Jobex.Sources.Company{__meta__: #Ecto.Schema.Metadata<:loaded, "companies">, id: "05d333c5-d248-44c3-bd36-d21beb7f064a", name: "omise.co", country: "remote", positions: #Ecto.Association.NotLoaded<association :positions is not loaded>, contacts: #Ecto.Association.NotLoaded<association :contacts is not loaded>, inserted_at: ~U[2025-05-03 17:03:01Z], updated_at: ~U[2025-05-03 17:03:01Z]},
...

Once you change handle_params/3 to match just to a tuple without :ok{pets, meta}, it works.
Here is the complete versions of the live view module:

defmodule JobexWeb.CompanyLive.Index do
  use JobexWeb, :live_view

  alias Jobex.Sources

@impl true
  def handle_params(params, _, socket) do
    # {:ok, {companies, meta}} = Sources.list_companies(params)
    {companies, meta} = Sources.list_companies(params)
    {:noreply, socket |> assign(:meta, meta) |> stream(:companies, companies, reset: true)}
  end

...
@impl true
  def render(assigns) do
    ~H"""
    <.header>
      Listing Companies
    </.header>

    <Flop.Phoenix.table items={@streams.companies} meta={@meta} path={~p"/companies"}>
      <:col :let={{_, company}} label="Name" field={:name}>{company.name}</:col>
      <:col :let={{_, company}} label="Country" field={:age}>{company.country}</:col>
    </Flop.Phoenix.table>

    <Flop.Phoenix.pagination meta={@meta} path={~p"/companies"} />

    """
  end

Here is the context module Sources:

def list_companies(params) do
    Flop.validate_and_run!(Company, params, for: Company, replace_invalid_params: true)
  end

Is there a way to pass tailwind classes to the table and its rows? Otherwise the styling is not so fancy, almost non-existing :face_with_raised_eyebrow:

Thank you

P.S. For unknown reason, both VS Code and Zed raise the compilation error once I add the dependency to the Mix file. I’m using these versions:

elixir 1.18.0-otp-27
erlang 27.0

Thank you, I fixed the example.

Right, the library doesn’t come with any default styles. You can pass classes with the *_attrs options and attributes. Some via the opts attribute, and some with the attributes on the :col slot.

1 Like

Ah, ok, thank you very much, I’ll have to look through the available documentation to have a clearer idea about custom classes/attributes to pass in :grinning_face:

No need for raised eyebrows—Flop is a very powerful and complete library! Anything I’ve felt I should be able to do has been possible. However, it can definitely feel a little daunting at times and some stuff has a bit of a verbose API, but there are a lot of Flop users around here so certainly feel free to ask questions!

Not to sound like a bootlicker with woylie in the chat but FlopPhoenix+LetMe has been a staple for new (non-Ash) projects to replace Kamari+Pundit (and of course Flop does more than Kamanari).

2 Likes