How to append to existing URL state in LiveView

I’m wondering how do people manage url state in LiveView when you want to append to the existing URL params? For example, in a todo app if we have two dropdowns that manage their own query param. So maybe one dropdown is to filter based off user and another dropdown is to filter based off project. If you select an option from the dropdown, you want to just update that section of the query params. I was wondering if I can do this through push_patch but it feels like then you need to manage the query param state.

I’m curious how other people are approaching this. I found this blog post helpful and might go about it this way but something tells me things can be simpler. Something like push_patch/3 could maybe be nice in this case. I’m not sure if it’s possible but if we had the previous params when we use push_patch that could be doable. Something like push_patch(socket, previous_params, opts) could help dynamically create your next path to navigate to using the existing params and seems like it works well with the lifecycle of LiveView.

Wondered about this myself and what I’ve found here and elsewhere is that when you need it you manage it explicitly in your LiveView.

The handle_params/3 callback gives you the current URI.

You can explicitly store the params (after filtering/validating them, as they are user input and shall not be trusted blindly) in the socket assigns.

Then have a private function operate on the stored params, e.g. using Map.merge/2.

You only need to store what you care about and when you care about, that’s why LiveView doesn’t store those by default, they’d be taking up space and not always be used.

2 Likes

I know @cjbottaro has a nice home-rolled version of this (sorry for the ping :grin:). I use Flop along with FlopPhoenix though that isn’t exactly a drop-in dependency if you already have solution for filter/sort/paging.

This is probably the simplest way I’ve seen. This is taken from the pragmatic studios phoenix course:

  def handle_params(params, _uri, socket) do
    socket =
      socket
      |> stream(:resources, Resources.filter(params), reset: true)
      |> assign(:filters, to_form(params))

    {:noreply, socket}
  end

  def handle_event("filter", params, socket) do
    params =
      params
      |> Map.take(~w(q status sort_by))
      |> Map.reject(fn {_, v} -> v == "" end)

    {:noreply, push_patch(socket, to: ~p"/resources?#{params}")}
  end

The filter event would be triggered by the filter form on change and submit. The params would get built(taking only the keys needed and rejecting empty string values) every time and used in push_patch. The push_patch triggers handle_params, the filter query runs and the filter form is reassigned with the params.