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…
- Does a library make sense?
- 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.