Prefill a LiveView form based on the URL parameters

Hello,

I’m trying to make a search page for some entities, and I want the URL to be shareable. I would like my search form to be prefilled with what’s in the url parameters (after interpretation).

From what I understant, the for option of the form just defines the key where to put the form data in the sumbmit and change events payloads.

If I want to prefill the form I have to pass params={@search_params} like this:

    <.simple_form
      :let={f}
      for={:search_params}
      phx-change="search"
      phx-submit="search"
      params={@search_params}
    >
      <.input field={{f, :fulltext}} label={gettext("Full text")} phx-debounce="1000" />
      <:actions>
        <.button><%= pgettext("action", "Search") %></.button>
      </:actions>
    </.simple_form>

But this does not seem to be documented and I have this warning :

warning: undefined attribute “params” for component AripWeb.CoreComponents.simple_form/1

Though it works.

Is there a more idiomatic way of doing what I want?

Thank you.

My deps

{:phoenix, "~> 1.7.0-rc.0", override: true},
{:phoenix_html, "~> 3.0"},
{:phoenix_live_view, "~> 0.18.3"},

The full code:

defmodule AripWeb.SearchLive do
  # work base https://arathunku.com/b/2021/handling-search-form-nicely-with-phoenix-liveview/

  use AripWeb, :live_view

  import Arip.Gettext

  @impl true
  def render(assigns) do
    ~H"""
    <.simple_form
      :let={f}
      for={:search_params}
      phx-change="search"
      phx-submit="search"
      params={@search_params}
    >
      <.input field={{f, :fulltext}} label={gettext("Full text")} phx-debounce="1000" />
      <:actions>
        <.button><%= pgettext("action", "Search") %></.button>
      </:actions>
    </.simple_form>

    <pre>
      @search_ran <%= @search_ran %>
    </pre>

    <ul :if={@search_ran}>
      <li :for={reco <- @results}>
        <%= inspect(reco) %>
      </li>
    </ul>
    """
  end

  @impl true
  def mount(_params, session, socket) do
    IO.inspect(session, label: "session")
    {:ok, assign(socket, results: [], result_count: 0, search_ran: false)}
  end

  @impl true
  def handle_params(params, _url, socket) do
    socket =
      case map_size(params) do
        0 -> assign(socket, search_params: empty_search(), results: [], search_ran: false)
        _ -> assign(socket, search_params: params, results: find_recos(params), search_ran: true)
      end

    socket.assigns.search_params |> IO.inspect(label: "socket.assigns.search_params")

    {:noreply, socket |> assign(:page_title, "Search")}
  end

  @impl true
  def handle_event("search", %{"search_params" => params}, socket) do
    params |> IO.inspect(label: "params")

    URI.encode_query(params) |> IO.inspect(label: "URI.encode_query(params)")
    {:noreply, push_patch(socket, replace: true, to: ~p"/search?#{params}")}
  end

  defp empty_search do
    %{"fulltext" => ""}
  end

  defp find_recos(_params) do
    [1, 2, 4]
  end
end

What you pass to for can either be an atom (as you described) or it needs to be an implementation of Phoenix.HTML.FormData, like e.g. an Ecto.Changeset struct.

Yes I could see that,

I do not have a changeset since the search itself is not an entity, and the fields of the search do not map 1:1 with the searched entity (for instance you want to search for multiple values in one given field).

I looked into implementing Phoenix.HTML.FormData for a custom struct but I was supposing there should be a way to just pass the data itself.

You can create a schemaless changeset or have an embedded_schema mapping to the form, which you build a changeset for. E.g.

Ecto.Changeset.change({%{fulltext: ""}, %{fulltext: :string}})
2 Likes

Oh right, I totally forgot about that. Thanks!