r00ster

r00ster

Hi,

I’d like to create a form in Phoenix 1.7 with one text field that has no underlying changeset. Previously I’d use form_for along with text_input. Now, I guess, I should use .simple_form but I don’t know what should be there in the for attribute. When I try to use @conn, as in the 1.6, I get the error: function Plug.Conn.fetch/2 is undefined (Plug.Conn does not implement the Access behaviour. If you are using get_in/put_in/update_in, you can specify the field to be accessed using Access.key!/1).

What is the proper way to create an unbound form in Phoenix 1.7?

Thanks

Showing Posts 1 to 9

patrickdm

patrickdm

Hello, from the Phoenix.HTML.Form — Phoenix.HTML v4.3.0 docs, it looks like you can use it with a Map:

With map data
form_for/4 expects as first argument any data structure that implements the Phoenix.HTML.FormData protocol. By default, Phoenix.HTML implements this protocol for Map.

This is useful when you are creating forms that are not backed by any kind of data layer. Let’s assume that we’re submitting a form to the :new action in the FooController:

<%= form_for @conn.params, Routes.foo_path(@conn, :new), fn f -> %>
  <%= text_input f, :contents %>
  <%= submit "Search" %>
<% end %>

hth

r00ster

r00ster OP

Hi,

thanks for the quick reply. This is exactly what I had before I started migrating to 1.7. Is this still the way that it should be done?

patrickdm

patrickdm

Sorry, I didn’t write any form since 1.5.7, :man_shrugging:
I’d just give @conn.params a try :grimacing:.

codeanpeace

codeanpeace

.simple_form is a component defined within the core_components.ex generated via mix phx.gen.new and is a simple wrapper around the Phoenix.Component.form/1 function provided by LiveView.

Using the for attribute

The for attribute can also be a map or an Ecto.Changeset. In such cases, a form will be created on the fly, and you can capture it using :let:

<.form
  :let={form}
  for={@changeset}
  phx-change="change_user"
>

However, such approach is discouraged in LiveView for two reasons:

  • LiveView can better optimize your code if you access the form fields using @form[:field] rather than through the let-variable form
  • Ecto changesets are meant to be single use. By never storing the changeset in the assign, you will be less tempted to use it across operations

source: Phoenix.Component.form/1

r00ster

r00ster OP

I see I’m mixing a couple of things here… To clarify more: I have a ‘standard’ (not LiveView) page and I wonder what is the proper way to create such form in Phoenix 1.7. Sorry for confusion.

codeanpeace

codeanpeace

A bit further down in the docs, there’s an example noting that an action attribute is required for standard forms.

Example: outside LiveView (regular HTTP requests)

The form component can still be used to submit forms outside of LiveView. In such cases, the action attribute MUST be given. Without said attribute, the form method and csrf token are discarded.

<.form :let={f} for={@changeset} action={Routes.comment_path(:create, @comment)}>
  <.input field={f[:body]} />
</.form>

In the example above, we passed a changeset to for and captured the value using :let={f}. This approach is ok outside of LiveViews, as there are no change tracking optimizations to consider.

source: Example: outside LiveView (regular HTTP requests)

GazeIntoTheAbyss

GazeIntoTheAbyss

I’m curious on this as well. Currently I just cheat and make a form component with no changeset. For example for creating a form to sort content based on when it was created I can use the below.
Heex:

  <.form
    let={f}
    id="time-sort-form"
    phx-target={@myself}
    phx-change="update">

    <.input 
      field={f[:time]} 
      type="select" 
      options=
      {
        [ {"Today", "today"}, 
          {"Week", "week"},
          {"Month", "month"},
          {"Year", "year"},
          {"All Time", "all time"}
        ]
      } />

    </.form>
</div>

The ex:

defmodule APP.SortLive.FormComponent do
  use APP, :live_component

  @impl true
  def handle_event("update", %{"time" => time}, socket) do
    username = socket.assigns.user.username
    time = time
    sort = "popular"

    {:noreply, socket |> push_patch(to: ~p"/user/#{username}/?#{[sort: sort, time: time]}")}
  end
end

I pretty much create a form without a changeset, then use a handle_event to deal with the input. I do similar for things like search inputs as well, but my handle_event passes the query value to a function for the redirect.

I’d also like a nice neat example of a changeset free form as I’m pretty sure I’m doing this either wrong or ineffeciently. It works, but feels off.

kokolegorille

kokolegorille

I think it looks like this…

  def mount(_params, _session, socket) do
    {
      :ok,
      socket
      |> assign_form(%{})
    }
  end

You use an empty map as a changeset. Then in the form…

<.simple_form for={@form} as={:form} id="form-graph" phx-submit="save">

As You use normal controllers, it might look a little bit different, especially phx-submit won’t be available. But it should be the way to have a form without changeset.

BTW it’s how phx.gen.auth does when You select non live…

<.simple_form :let={f} for={@conn.params["user"]} as={:user} action={~p"/users/log_in"}>
codeanpeace

codeanpeace

Seems reasonable to me, .form automatically sets the :for assign to %{} by default when it’s not explicitly given.

https://github.com/phoenixframework/phoenix_live_view/blob/0058afeab2f2ad500b7285368c639a0d7bf8f2a0/lib/phoenix_component.ex#L2130-L2138

If you wanted to show which option is currently selected, you could then explicitly set a form assign so that the input component can pull the field value off of the form when generating the options via <%= Phoenix.HTML.Form.options_for_select(@options, @value) %>.

  <.form
    let={f}
    for={@form}
    id="time-sort-form"
    phx-target={@myself}
    phx-change="update">
      <.input 
        type="select" 
        field={f[:time]} 
        options={@time_options}
      />
  </.form>
</div>
defmodule MyApp.SortLive.FormComponent do
  use MyApp, :live_component

  def mount(socket) do
    time_form = to_form(%{"time" => "today"}
    time_options = [{"Today", "today"}, ..., {"All Time", "all time"}]

    {:ok, 
     socket
     |> assign(:sort, "popular")
     |> assign(:time_form, time_form)
     |> assign(:time_options, time_options)}
  end

  def handle_event("update", %{"time" => time}, socket) do
    username = socket.assigns.user.username
    sort = socket.assigns.sort
    time_form = to_form(%{"time" => time})

    {:noreply,
     socket
     |> assign(:time_form, time_form)
     |> push_patch(to: ~p"/user/#{username}/?#{[sort: sort, time: time]}")}
  end
end
— All posts loaded —

Where Next? Top

Trending in Questions Top

Blokh
Hey guys, I’ve got a huge CSV ( around 10 GB ) that needs to be processed hourly Do you guys have any suggestions what is the best prac...
New
kszambelanczyk
Hello! Could someone please give me a help/sample code, how to delete a file from s3 using waffle/waffle_ecto from Phoenix app. I creat...
New
RemyXRenard
I’m seeing that a list inside a Kino.DataTable will be interpreted as a charlist, even if the Kino.configure() is set to charlists: :as_l...
New
matt-savvy
Anyone here using Honeybadger? My Honeybadger account is being overwhelmed with noise from some bots. Seeing a lot of Bandit.HTTPError...
New
velrest
So my question is quite simple and i have found no conclusive answer on forum, google or AI. Should we use :erlang.float for Integer to ...
New
samoloth
Hi, I’ve just set up an application with ash_authentication. There is only magic link strategy for now, so there is no confirmation add o...
New
FlyingNoodle
If a change or preparation module uses Ash.Changeset.get_argument/2 or Ash.Query.get_argument/2 (or any of the other get_argument functio...
New

Other Trending Topics Top

mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
mcass19
ExRatatui lets you cook up rich terminal UIs in Elixir, powered by Rust’s ratatui via Rustler NIFs. Build interactive terminal applicatio...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge &amp; Solve. They are GUI (Emerge) and State management (S...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews