Push_patch appending/merging params, library or builtin?

There have been a few posts regarding this topic…

I ended up making a little library for this and it seems to work well.

My two questions are…

  1. Does a library make sense?
  2. If so, why shouldn’t it just be in LiveView?

Here’s the API for the little library…

# Update page, but leave path and other params as is.
push_patch_params(socket, page: 1) # returns socket (after push_patch)
patch_params(socket, page: 1)      # returns string

# Delete page, but leave path and other params as is.
push_patch_params(socket, page: nil) # returns socket (after push_patch)
patch_params(socket, page: nil)      # returns string

If it were to just be implemented in LiveView, it seems easy to just expand the API of push_patch:

push_patch(socket, to: ~p"/my/page/#{params}")
push_patch(socket, params: params)
push_patch(socket, params: params, replace: true)

The library is really simple and only 50 lines of code…

defmodule Web.Scholarship.PatchParams do
  import Phoenix.LiveView
  import Phoenix.Component

  defstruct [:uri, :params]

  defmacro __using__(_opts \\ []) do
    quote do
      on_mount Web.Scholarship.PatchParams
      import Web.Scholarship.PatchParams
    end
  end

  def on_mount(:default, _params, _session, socket) do
    socket = attach_hook(socket, __MODULE__, :handle_params, fn unsigned_params, uri, socket ->
      {:cont, assign(socket, __MODULE__, %__MODULE__{uri: URI.parse(uri), params: unsigned_params})}
    end)

    {:cont, socket}
  end

  def patch_params(%Phoenix.LiveView.Socket{assigns: assigns}, params) do
    patch_params(assigns, params)
  end

  def patch_params(%{__MODULE__ => %{uri: uri, params: old_params}}, params) do
    params = Map.new(params)
    |> Aw.Map.stringify_keys()

    params = Map.merge(old_params, params)
    |> Aw.Map.compact()
    |> Plug.Conn.Query.encode()

    "#{uri.path}?#{params}"
    |> String.replace_suffix("?", "")
  end

  def patch_params(_, _params) do
    raise "#{inspect __MODULE__} not initialized; did you forget to add an on_mount hook or are you calling from mount/3?"
  end

  def push_patch_params(%Phoenix.LiveView.Socket{} = socket, params) do
    push_patch(socket, to: patch_params(socket, params))
  end

end

It uses a “hidden” assign that is set in a handle_params hook. If I were to build this into Phoenix, I think using private or even extending Phoenix.LiveView.Socket would be better.

Here’s an example of it in use…

def handle_event("search", params, socket) do
  changeset = search_changeset(params)

  socket = case Ecto.Changeset.apply_changes(changeset, :update) do
    {:ok, search} ->
      push_patch_params(socket, term: search.term, sort: search.sort, page: nil)

    {:error, changeset} ->
      assign(socket, :form, to_form(changeset, action: :update))
  end

  {:noreply, socket}
end

What do y’all think? I feel like there some obvious stuff that I’m missing.

P.S. I know flop does a lot of this stuff for you, but I’m currently enjoying minimal abstractions.

1 Like

I use the pattern Jose describes in the first link, using ~p"" these days. The path is sometimes conditional on assigns, so if it were built-in, that would need to be overrideable.

Yeah, it’s doing the technique described in that post, just wrapped it up in a little library that stores the params for you via a hook and then gives a nice API to push_patch for only changing one or two params while leaving the rest (and path) untouched.

As for needing to be overridable, it wouldn’t be replacing push_patch with :to, but augmenting it to work with a :params option for this seemingly very common use case.

with verified routes i’d love some way to have a helper like

<.link
                navigate={merge_query(~p"/information/search", @params, %{query => "this is a test"})}
                class="px-3 py-2"
              >

where it would verify the route, relies on the params assigned but merge the third argument params in. If one had a value of nil, it would remove it on merge.

I believe jose’s solution is pre-verified-routes